Repository: Outerbridgeio/Outerbridge Branch: master Commit: e2aaae99205b Files: 317 Total size: 2.3 MB Directory structure: gitextract_dr2lrn6b/ ├── .dockerignore ├── .eslintrc.js ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows/ │ └── main.yml ├── .gitignore ├── .husky/ │ └── pre-commit ├── .prettierignore ├── .prettierrc.js ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE.md ├── README.md ├── babel.config.js ├── docker/ │ ├── Dockerfile │ └── docker-compose.yml ├── package.json ├── packages/ │ ├── components/ │ │ ├── README.md │ │ ├── credentials/ │ │ │ ├── Alchemy/ │ │ │ │ └── AlchemyApi.ts │ │ │ ├── Arbiscan/ │ │ │ │ └── ArbiscanApi.ts │ │ │ ├── Binance/ │ │ │ │ └── BinanceApi.ts │ │ │ ├── Bscscan/ │ │ │ │ └── BscscanApi.ts │ │ │ ├── Celoscan/ │ │ │ │ └── CeloscanApi.ts │ │ │ ├── Cronosscan/ │ │ │ │ └── CronosscanApi.ts │ │ │ ├── EmailSend/ │ │ │ │ └── EmailSendSmtp.ts │ │ │ ├── Etherscan/ │ │ │ │ └── EtherscanApi.ts │ │ │ ├── Fantomscan/ │ │ │ │ └── FantomscanApi.ts │ │ │ ├── FootprintAnalytics/ │ │ │ │ └── FootprintAnalyticsApi.ts │ │ │ ├── GitHub/ │ │ │ │ └── GitHubApi.ts │ │ │ ├── Gnosisscan/ │ │ │ │ └── GnosisscanApi.ts │ │ │ ├── GoogleDocs/ │ │ │ │ └── GoogleDocsOAuth2.ts │ │ │ ├── GoogleSheet/ │ │ │ │ └── GoogleSheetOAuth2.ts │ │ │ ├── HTTP/ │ │ │ │ ├── HTTPBasicAuth.ts │ │ │ │ └── HTTPBearerTokenAuth.ts │ │ │ ├── Helio/ │ │ │ │ └── HelioApi.ts │ │ │ ├── Helius/ │ │ │ │ └── HeliusApi.ts │ │ │ ├── Hubspot/ │ │ │ │ └── HubspotApi.ts │ │ │ ├── HuggingFace/ │ │ │ │ └── HuggingFaceAccessToken.ts │ │ │ ├── Imap/ │ │ │ │ └── Imap.ts │ │ │ ├── Infura/ │ │ │ │ └── InfuraApi.ts │ │ │ ├── Mailchimp/ │ │ │ │ └── MailchimpApi.ts │ │ │ ├── MoonBeamScan/ │ │ │ │ └── MoonBeamScanApi.ts │ │ │ ├── MoonRiverScan/ │ │ │ │ └── MoonRiverScanApi.ts │ │ │ ├── Moralis/ │ │ │ │ └── MoralisApi.ts │ │ │ ├── Notion/ │ │ │ │ └── NotionApi.ts │ │ │ ├── OpenAI/ │ │ │ │ └── OpenAIApi.ts │ │ │ ├── Opensea/ │ │ │ │ └── OpenseaApi.ts │ │ │ ├── OptimisticEtherscan/ │ │ │ │ └── OptimisticEtherscanApi.ts │ │ │ ├── Pinata/ │ │ │ │ └── PinataApi.ts │ │ │ ├── Polygonscan/ │ │ │ │ └── PolygonscanApi.ts │ │ │ ├── QuickNode/ │ │ │ │ └── QuickNodeEndpoints.ts │ │ │ ├── RequestFinanceApi/ │ │ │ │ └── RequestFinanceApi.ts │ │ │ ├── SnowTrace/ │ │ │ │ └── SnowTraceApi.ts │ │ │ ├── Telegram/ │ │ │ │ └── TelegramApi.ts │ │ │ ├── Twitter/ │ │ │ │ └── TwitterApi.ts │ │ │ ├── Typeform/ │ │ │ │ └── TypeformApi.ts │ │ │ ├── TypeformWebhook/ │ │ │ │ └── TypeformApi.ts │ │ │ └── Xero/ │ │ │ └── XeroOAuth2.ts │ │ ├── gulpfile.ts │ │ ├── nodes/ │ │ │ ├── Alchemy/ │ │ │ │ ├── Alchemy.ts │ │ │ │ ├── AlchemyTrigger.ts │ │ │ │ ├── AlchemyWebhook.ts │ │ │ │ ├── extendedOperation.ts │ │ │ │ ├── solanaOperation.ts │ │ │ │ ├── subscribeOperation.ts │ │ │ │ └── supportedNetwork.ts │ │ │ ├── Arbiscan/ │ │ │ │ ├── Arbiscan.ts │ │ │ │ └── constants.ts │ │ │ ├── BEP20Function/ │ │ │ │ ├── BEP20Function.ts │ │ │ │ └── helperFunctions.ts │ │ │ ├── BEP20Transfer/ │ │ │ │ └── BEP20Transfer.ts │ │ │ ├── BEP20TransferTrigger/ │ │ │ │ └── BEP20TransferTrigger.ts │ │ │ ├── BNBBalanceTrigger/ │ │ │ │ └── BNBBalanceTrigger.ts │ │ │ ├── BNBTransfer/ │ │ │ │ └── BNBTransfer.ts │ │ │ ├── Binance/ │ │ │ │ ├── BinancePrivate.ts │ │ │ │ └── BinancePublic.ts │ │ │ ├── BlockchainEvent/ │ │ │ │ └── BlockchainEvent.ts │ │ │ ├── BscScan/ │ │ │ │ ├── Bscscan.ts │ │ │ │ └── constants.ts │ │ │ ├── ChainLink/ │ │ │ │ ├── ChainLink.ts │ │ │ │ ├── ChainLinkFunctionWebhook.ts │ │ │ │ └── supportedNetwork.ts │ │ │ ├── ContractEventTrigger/ │ │ │ │ └── ContractEventTrigger.ts │ │ │ ├── ContractFunctionTrigger/ │ │ │ │ └── ContractFunctionTrigger.ts │ │ │ ├── CreateERC20Token/ │ │ │ │ └── CreateERC20Token.ts │ │ │ ├── CreateNFT/ │ │ │ │ └── CreateNFT.ts │ │ │ ├── Discord/ │ │ │ │ └── Discord.ts │ │ │ ├── ERC20Function/ │ │ │ │ ├── ERC20Function.ts │ │ │ │ └── helperFunctions.ts │ │ │ ├── ERC20Transfer/ │ │ │ │ └── ERC20Transfer.ts │ │ │ ├── ERC20TransferTrigger/ │ │ │ │ └── ERC20TransferTrigger.ts │ │ │ ├── ETHBalanceTrigger/ │ │ │ │ └── ETHBalanceTrigger.ts │ │ │ ├── ETHTransfer/ │ │ │ │ └── ETHTransfer.ts │ │ │ ├── EmailSend/ │ │ │ │ └── EmailSend.ts │ │ │ ├── EmailTrigger/ │ │ │ │ └── EmailTrigger.ts │ │ │ ├── Etherscan/ │ │ │ │ ├── Etherscan.ts │ │ │ │ └── constants.ts │ │ │ ├── ExecuteContractFunction/ │ │ │ │ └── ExecuteContractFunction.ts │ │ │ ├── FlowBalanceTrigger/ │ │ │ │ └── FlowBalanceTrigger.ts │ │ │ ├── FootprintAnalytics/ │ │ │ │ ├── FootprintAnalytics.ts │ │ │ │ └── constants.ts │ │ │ ├── GitHub/ │ │ │ │ ├── GitHub.ts │ │ │ │ └── GitHubWebhook.ts │ │ │ ├── GoogleDocs/ │ │ │ │ └── GoogleDocs.ts │ │ │ ├── GoogleSheet/ │ │ │ │ └── GoogleSheet.ts │ │ │ ├── GraphQL/ │ │ │ │ └── GraphQL.ts │ │ │ ├── HTTP/ │ │ │ │ └── HTTP.ts │ │ │ ├── Helio/ │ │ │ │ ├── Helio.ts │ │ │ │ └── HelioWebhook.ts │ │ │ ├── Helius/ │ │ │ │ └── Helius.ts │ │ │ ├── Hubspot/ │ │ │ │ └── Hubspot.ts │ │ │ ├── HuggingFace/ │ │ │ │ └── HuggingFace.ts │ │ │ ├── IfElse/ │ │ │ │ └── IfElse.ts │ │ │ ├── ImageEditor/ │ │ │ │ └── ImageEditor.ts │ │ │ ├── Infura/ │ │ │ │ ├── Infura.ts │ │ │ │ ├── InfuraTrigger.ts │ │ │ │ ├── extendedOperation.ts │ │ │ │ └── subscribeOperation.ts │ │ │ ├── MATICBalanceTrigger/ │ │ │ │ └── MATICBalanceTrigger.ts │ │ │ ├── MATICTransfer/ │ │ │ │ └── MATICTransfer.ts │ │ │ ├── Mailchimp/ │ │ │ │ └── Mailchimp.ts │ │ │ ├── Moralis/ │ │ │ │ ├── Moralis.ts │ │ │ │ ├── extendedDeFiOperation.ts │ │ │ │ ├── extendedEVMOperation.ts │ │ │ │ ├── extendedNFTOperation.ts │ │ │ │ └── supportedNetwork.ts │ │ │ ├── NFTMintTrigger/ │ │ │ │ └── NFTMintTrigger.ts │ │ │ ├── NFTTransferTrigger/ │ │ │ │ └── NFTTransferTrigger.ts │ │ │ ├── NodeJS/ │ │ │ │ └── NodeJS.ts │ │ │ ├── Notion/ │ │ │ │ └── Notion.ts │ │ │ ├── OpenAI/ │ │ │ │ └── OpenAI.ts │ │ │ ├── Opensea/ │ │ │ │ ├── Opensea.ts │ │ │ │ ├── OpenseaEventTrigger.ts │ │ │ │ └── extendedParameters.ts │ │ │ ├── OptimismScan/ │ │ │ │ ├── OptimismScan.ts │ │ │ │ └── constants.ts │ │ │ ├── PancakeSwap/ │ │ │ │ ├── PancakeSwap.ts │ │ │ │ ├── abis/ │ │ │ │ │ └── WBNB.json │ │ │ │ └── extendedTokens.ts │ │ │ ├── Pinata/ │ │ │ │ └── Pinata.ts │ │ │ ├── PolygonScan/ │ │ │ │ ├── PolygonScan.ts │ │ │ │ └── constants.ts │ │ │ ├── QuickNode/ │ │ │ │ ├── QuickNode.ts │ │ │ │ ├── QuickNodeTrigger.ts │ │ │ │ ├── extendedOperation.ts │ │ │ │ ├── solanaOperation.ts │ │ │ │ ├── subscribeOperation.ts │ │ │ │ └── supportedNetwork.ts │ │ │ ├── RequestFinance/ │ │ │ │ ├── RequestFinance.ts │ │ │ │ ├── RequestFinanceTrigger.ts │ │ │ │ └── constants.ts │ │ │ ├── Scheduler/ │ │ │ │ └── Scheduler.ts │ │ │ ├── Slack/ │ │ │ │ └── Slack.ts │ │ │ ├── SnowTrace/ │ │ │ │ └── SnowTrace.ts │ │ │ ├── Solidity/ │ │ │ │ └── Solidity.ts │ │ │ ├── Solscan/ │ │ │ │ ├── Solscan.ts │ │ │ │ └── constants.ts │ │ │ ├── Teams/ │ │ │ │ └── Teams.ts │ │ │ ├── Telegram/ │ │ │ │ └── Telegram.ts │ │ │ ├── ThirdWeb/ │ │ │ │ ├── ThirdWeb.ts │ │ │ │ ├── ThirdWebTrigger.ts │ │ │ │ ├── constants.ts │ │ │ │ └── supportedNetwork.ts │ │ │ ├── Twitter/ │ │ │ │ └── Twitter.ts │ │ │ ├── Typeform/ │ │ │ │ └── Typeform.ts │ │ │ ├── TypeformWebhook/ │ │ │ │ └── TypeformWebhook.ts │ │ │ ├── Uniswap/ │ │ │ │ ├── Uniswap.ts │ │ │ │ └── nativeTokens.ts │ │ │ ├── Wait/ │ │ │ │ └── Wait.ts │ │ │ ├── Webhook/ │ │ │ │ └── Webhook.ts │ │ │ └── Xero/ │ │ │ └── xero.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── ChainNetwork.ts │ │ │ ├── ETHOperations.ts │ │ │ ├── Interface.ts │ │ │ ├── abis/ │ │ │ │ ├── WBNB.json │ │ │ │ └── WETH.json │ │ │ ├── index.ts │ │ │ └── utils.ts │ │ └── tsconfig.json │ ├── server/ │ │ ├── README.md │ │ ├── babel.config.js │ │ ├── bin/ │ │ │ ├── dev │ │ │ ├── dev.cmd │ │ │ ├── run │ │ │ └── run.cmd │ │ ├── nodemon.json │ │ ├── oauth2.html │ │ ├── package.json │ │ ├── src/ │ │ │ ├── ActiveTestTriggerPool.ts │ │ │ ├── ActiveTestWebhookPool.ts │ │ │ ├── ChildProcess.ts │ │ │ ├── CredentialsPool.ts │ │ │ ├── DataSource.ts │ │ │ ├── DeployedWorkflowPool.ts │ │ │ ├── Interface.ts │ │ │ ├── NodesPool.ts │ │ │ ├── commands/ │ │ │ │ └── start.ts │ │ │ ├── entity/ │ │ │ │ ├── Contract.ts │ │ │ │ ├── Credential.ts │ │ │ │ ├── Execution.ts │ │ │ │ ├── Wallet.ts │ │ │ │ ├── Webhook.ts │ │ │ │ └── Workflow.ts │ │ │ ├── index.ts │ │ │ └── utils/ │ │ │ └── index.ts │ │ └── tsconfig.json │ └── ui/ │ ├── .npmignore │ ├── README.md │ ├── jsconfig.json │ ├── package.json │ ├── public/ │ │ └── index.html │ └── src/ │ ├── App.js │ ├── api/ │ │ ├── apikey.js │ │ ├── client.js │ │ ├── contracts.js │ │ ├── credential.js │ │ ├── executions.js │ │ ├── nodes.js │ │ ├── oauth2.js │ │ ├── wallets.js │ │ ├── webhooks.js │ │ └── workflows.js │ ├── assets/ │ │ └── scss/ │ │ ├── _themes-vars.module.scss │ │ └── style.scss │ ├── config.js │ ├── hooks/ │ │ ├── useApi.js │ │ ├── useConfirm.js │ │ └── useScriptRef.js │ ├── index.js │ ├── layout/ │ │ ├── MainLayout/ │ │ │ ├── Header/ │ │ │ │ └── index.js │ │ │ ├── LogoSection/ │ │ │ │ └── index.js │ │ │ ├── Sidebar/ │ │ │ │ ├── MenuList/ │ │ │ │ │ ├── NavCollapse/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── NavGroup/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── NavItem/ │ │ │ │ │ │ └── index.js │ │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ │ └── index.js │ │ ├── MinimalLayout/ │ │ │ └── index.js │ │ ├── NavMotion.js │ │ └── NavigationScroll.js │ ├── menu-items/ │ │ ├── dashboard.js │ │ ├── index.js │ │ └── settings.js │ ├── routes/ │ │ ├── CanvasRoutes.js │ │ ├── MainRoutes.js │ │ └── index.js │ ├── serviceWorker.js │ ├── store/ │ │ ├── actions.js │ │ ├── constant.js │ │ ├── context/ │ │ │ ├── ConfirmContext.js │ │ │ └── ConfirmContextProvider.js │ │ ├── index.js │ │ ├── reducer.js │ │ └── reducers/ │ │ ├── canvasReducer.js │ │ ├── customizationReducer.js │ │ ├── dialogReducer.js │ │ └── notifierReducer.js │ ├── themes/ │ │ ├── compStyleOverride.js │ │ ├── index.js │ │ ├── palette.js │ │ └── typography.js │ ├── ui-component/ │ │ ├── Loadable.js │ │ ├── Loader.js │ │ ├── Logo.js │ │ ├── StyledButton.js │ │ ├── StyledFab.js │ │ ├── TooltipWithParser.js │ │ ├── cards/ │ │ │ ├── ItemCard.js │ │ │ ├── MainCard.js │ │ │ └── Skeleton/ │ │ │ └── WorkflowCard.js │ │ ├── dialog/ │ │ │ ├── AttachmentDialog.js │ │ │ ├── ConfirmDialog.js │ │ │ ├── EditVariableDialog.css │ │ │ ├── EditVariableDialog.js │ │ │ ├── ExpandDataDialog.js │ │ │ ├── HTMLDialog.js │ │ │ ├── SaveWorkflowDialog.js │ │ │ └── TestWorkflowDialog.js │ │ ├── editor/ │ │ │ ├── DarkCodeEditor.js │ │ │ ├── LightCodeEditor.js │ │ │ ├── prism-dark.css │ │ │ └── prism-light.css │ │ └── extended/ │ │ ├── AnimateButton.js │ │ ├── Avatar.js │ │ ├── Breadcrumbs.js │ │ └── Transitions.js │ ├── utils/ │ │ ├── genericHelper.js │ │ ├── useNotifier.js │ │ └── usePrompt.js │ └── views/ │ ├── apikey/ │ │ ├── APIKeyDialog.js │ │ └── index.js │ ├── canvas/ │ │ ├── AddNodes.js │ │ ├── ButtonEdge.js │ │ ├── CanvasHeader.js │ │ ├── CanvasNode.js │ │ ├── EditNodes.js │ │ ├── VariableSelector.js │ │ ├── index.css │ │ └── index.js │ ├── contracts/ │ │ ├── ContractDialog.js │ │ └── index.js │ ├── executions/ │ │ └── index.js │ ├── inputs/ │ │ ├── ArrayInputParameters.js │ │ ├── AsyncSelectWrapper.js │ │ ├── CredentialInput.js │ │ ├── InputParameters.css │ │ ├── InputParameters.js │ │ ├── OptionParamsResponse.css │ │ └── OptionParamsResponse.js │ ├── output/ │ │ └── OutputResponses.js │ ├── settings/ │ │ └── index.js │ ├── wallets/ │ │ ├── WalletDialog.js │ │ └── index.js │ └── workflows/ │ └── index.js └── turbo.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ node_modules dist build **/node_modules **/build **/dist ================================================ FILE: .eslintrc.js ================================================ module.exports = { extends: [ 'eslint:recommended', 'plugin:markdown/recommended', 'plugin:react/recommended', 'plugin:react/jsx-runtime', 'plugin:react-hooks/recommended', 'plugin:jsx-a11y/recommended', 'plugin:prettier/recommended' ], settings: { react: { version: 'detect' } }, parser: '@typescript-eslint/parser', ignorePatterns: ['**/node_modules', '**/dist', '**/build', '**/package-lock.json'], plugins: ['unused-imports'], rules: { '@typescript-eslint/explicit-module-boundary-types': 'off', 'no-unused-vars': 'off', 'unused-imports/no-unused-imports': 'warn', 'unused-imports/no-unused-vars': ['warn', { vars: 'all', varsIgnorePattern: '^_', args: 'after-used', argsIgnorePattern: '^_' }], 'no-undef': 'off', 'no-console': [process.env.CI ? 'error' : 'warn', { allow: ['warn', 'error', 'info'] }], 'prettier/prettier': 'error' } } ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: "[BUG]" labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Setup** - OS: [e.g. iOS, Windows, Linux] - Browser [e.g. chrome, safari] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: "[FEATURE]" labels: '' assignees: '' --- **Describe the feature you'd like** A clear and concise description of what you would like Outerbridge to have. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/workflows/main.yml ================================================ name: Node CI on: push: branches: - master pull_request: branches: - '*' permissions: contents: read jobs: build: strategy: matrix: platform: [ubuntu-latest] node-version: [14.x, 16.x] runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - run: npm i -g yarn - run: yarn install --ignore-engines - run: yarn lint - run: yarn build ================================================ FILE: .gitignore ================================================ # editor .idea .vscode # dependencies **/node_modules **/package-lock.json **/yarn.lock ## logs **/*.log ## build **/dist **/build ## temp **/tmp **/temp ## test **/coverage # misc .DS_Store ## env .env.local .env.development.local .env.test.local .env.production.local **/.env ## turbo .turbo ## secrets **/*.key **/api.json ## compressed **/*.tgz ================================================ FILE: .husky/pre-commit ================================================ #!/bin/sh . "$(dirname "$0")/_/husky.sh" yarn quick # prettify yarn lint-staged # eslint lint(also include prettify but prettify support more file extensions than eslint, so run prettify first) ================================================ FILE: .prettierignore ================================================ **/node_modules **/dist **/build ================================================ FILE: .prettierrc.js ================================================ module.exports = { printWidth: 140, singleQuote: true, jsxSingleQuote: true, trailingComma: 'none', tabWidth: 4, semi: false, endOfLine: 'auto' } ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hello@outerbridge.io. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Outerbridge As the old chinese saying goes: 礼轻情意重 (It's the thought that counts), we appreciate any form of contributions. ## ⭐ Star Star and share the [Github Repo](https://github.com/Outerbridgeio/Outerbridge). ## 🙋 Q&A Search up for any questions in [Q&A section](https://github.com/Outerbridgeio/Outerbridge/discussions/categories/q-a), if you can't find one, don't hesitate to create one. It might helps others that have similar question. ## 🙌 Share Workflow Yes! Sharing how you use Outerbridge is a way of contribution. Export your workflow as JSON, attach a screenshot and share it in [Show and Tell section](https://github.com/Outerbridgeio/Outerbridge/discussions/categories/show-and-tell). ## 💡 Ideas Ideas are welcome such as new feature, apps integration, and blockchain networks. Submit in [Ideas section](https://github.com/Outerbridgeio/Outerbridge/discussions/categories/ideas). ## 🐞 Report Bugs Found an issue? [Report it](https://github.com/Outerbridgeio/Outerbridge/issues/new/choose). ## 👨‍💻 Contribute to Code Not sure what to contribute? Some ideas: - Create new node/credential component - Update existing components such as extending functionality, fixing bugs - Add new blockchain network to support - Unit tests and E2E tests ### Developers Outerbridge has 3 different modules in a single mono repository. - `server`: Node backend to serve API logics - `ui`: React frontend - `components`: Nodes and Credentials of applications #### Prerequisite - Install MongoDB [here](https://www.mongodb.com/try/download/community?tck=docs_server) - Install Yarn ```bash npm i -g yarn ``` #### Step by step 1. Fork the official [Outerbridge Github Repository](https://github.com/Outerbridgeio/Outerbridge). 2. Clone your forked repository. 3. Create a new branch, see [guide](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-and-deleting-branches-within-your-repository). Naming conventions: - For feature branch: `feature/` - For bug fix branch: `bugfix/`. 4. Switch to the newly created branch. 5. Go into repository folder ```bash cd Outerbridge ``` 6. Install all dependencies of all modules: ```bash yarn install ``` 7. Build all the code: ```bash yarn build ``` 8. Start the app on [http://localhost:3000](http://localhost:3000) ```bash yarn start ``` 9. For development, run ```bash yarn dev ``` Any changes made in `packages/ui` or `packages/server` will be reflected on [http://localhost:8080](http://localhost:8080) For changes made in `packages/components`, run `yarn build` again to pickup the changes. 10. After making all the changes, run ```bash yarn build ``` and ```bash yarn start ``` to make sure everything works fine in production. 11. Commit code and submit Pull Request from forked branch pointing to [Outerbridge master](https://github.com/Outerbridgeio/Outerbridge/tree/master). Example [PR](https://github.com/Outerbridgeio/Outerbridge/pull/50). ## 📖 Contribute to Docs Contribute to Outerbridge [docs](https://github.com/Outerbridgeio/outerbridge-docs) if there is any mistake, confusion or grap. ## 🏷️ Pull Request process A member of the Outerbridge team will automatically be notified/assigned when you open a pull request. You can also reach out to us on [Discord](https://discord.gg/Y9VE4ykPDJ). ## 📃 Contributor License Agreement Before we can merge your contribution you have to sign our [Contributor License Agreement (CLA)](https://cla-assistant.io/OuterbridgeIO/Outerbridge). The CLA contains the terms and conditions under which the contribution is submitted. You need to do this only once for your first pull request. Keep in mind that without a signed CLA we cannot merge your contribution. ## 📜 Code of Conduct This project and everyone participating in it are governed by the Code of Conduct which can be found in the [file](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to henryheng@outerbridge.io or hello@outerbridge.io. ================================================ FILE: Dockerfile ================================================ # Build local monorepo image # docker build --no-cache -t outerbridge . # Run image # docker run -d -p 3000:3000 outerbridge FROM node:16 WORKDIR /usr/src/packages # Copy root package.json and lockfile COPY package.json ./ COPY yarn.lock ./ # Copy components package.json COPY packages/components/package.json ./packages/components/package.json # Copy ui package.json COPY packages/ui/package.json ./packages/ui/package.json # Copy server package.json COPY packages/server/package.json ./packages/server/package.json RUN yarn install # Copy app source COPY . . RUN yarn build EXPOSE 3000 CMD [ "yarn", "start" ] ================================================ FILE: LICENSE.md ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: README.md ================================================ # Outerbridge - Automate Web3 and Web2 applications Outerbridge is a low code/no code workflow automation application, focusing on integrating both on-chain and off-chain applications. The project is licensed under [Apache License Version 2.0](LICENSE.md), source available and free to self-host. ![Outerbridge](./assets/outerbridge_brand.png) ![Outerbridge Screenshot](./assets/screenshot_outerbridge.jpg) ## 💡Why another workflow automation tool? There are many awesome automation tools out there, however there isn't one that has the built-in logic of interacting/consuming information from blockchains. Hence, Outerbridge is created to allow people building workflows involving on-chain and off-chain applications, with simple drag and drop interface. ## ⚡Quick Start Watch [Outerbridge Quickstart Demo](https://www.youtube.com/watch?v=x-AfrkKvZ4M) on Youtube (4mins) 1. Install MongoDB [locally](https://www.mongodb.com/docs/manual/administration/install-community/) OR follow the guide of using MongoDB Atlas [here](https://docs.outerbridge.io/get-started#mongodb-atlas) 2. Install Outerbridge ```bash npm install -g outerbridge ``` 3. Start Outerbridge ```bash npx outerbridge start ``` If using MongoDB Atlas ```bash npx outerbridge start --mongourl=mongodb+srv://:@.mongodb.net/outerbridge?retryWrites=true&w=majority ``` 4. Open [http://localhost:3000](http://localhost:3000) ## 🐳 Docker 1. Go to `docker` folder at the root of the project 2. `docker-compose up -d` 3. This will automatically spins up mongodb and outerbridge containers 4. Open [http://localhost:3000](http://localhost:3000) 5. You can bring the containers down by `docker-compose stop` 6. If using MongoDB Atlas, follow the guide [here](https://docs.outerbridge.io/get-started#-docker) ## 👨‍💻 Developers Outerbridge has 3 different modules in a single mono repository. - `server`: Node backend to serve API logics - `ui`: React frontend - `components`: Nodes and Credentials of applications ### Prerequisite - Install MongoDB [locally](https://www.mongodb.com/docs/manual/administration/install-community/) OR register a MongoDB Atlas [here](https://www.mongodb.com/atlas/database) - Install Yarn ```bash npm i -g yarn ``` ### Setup 1. Clone the repository ```bash git clone https://github.com/Outerbridgeio/Outerbridge.git ``` 2. Go into repository folder ```bash cd Outerbridge ``` 3. Install all dependencies of all modules: ```bash yarn install ``` 4. Build all the code: ```bash yarn build ``` 5. Start the app: ```bash yarn start ``` You can now access the app on [http://localhost:3000](http://localhost:3000) 6. For development build: ```bash yarn dev ``` Any code changes will reload the app automatically on [http://localhost:8080](http://localhost:8080) ## 📖 Documentation Official Outerbridge docs can be found under: [https://docs.outerbridge.io](https://docs.outerbridge.io) ## 💻 Cloud Hosted - [Cloud Hosted](https://app.outerbridge.io) version of Outerbridge. ## 🌐 Self Host - Digital Ocean Droplet: [Setup guide](https://gist.github.com/HenryHengZJ/93210d43d655b4172ee50794ce473b62) - AWS EC2: [Setup guide](https://gist.github.com/HenryHengZJ/627cec19671664a88754c7e383232dc8) ## 🙋 Support Feel free to ask any questions, raise problems, and request new features in [discussion](https://github.com/Outerbridgeio/Outerbridge/discussions) ## 🙌 Contributing See [contributing guide](CONTRIBUTING.md). Reach out to us at [Discord](https://discord.gg/Y9VE4ykPDJ) if you have any questions or issues. ## 📄 License Source code in this repository is made available under the [Apache License Version 2.0](LICENSE.md). ================================================ FILE: babel.config.js ================================================ module.exports = { presets: [ '@babel/preset-typescript', [ '@babel/preset-env', { targets: { node: 'current' } } ] ] } ================================================ FILE: docker/Dockerfile ================================================ FROM node:14.20.0-alpine USER root RUN apk add --no-cache git # You can install a specific version like: outerbridge@1.0.5 RUN npm install -g outerbridge WORKDIR /data CMD "outerbridge" ================================================ FILE: docker/docker-compose.yml ================================================ version: '3.1' services: mongo: image: mongo ports: - "27017:27017" restart: always environment: - MONGO_INITDB_DATABASE=outerbridge outerbridge: image: outerbridgeio/outerbridge restart: always environment: - MONGO_HOST=${MONGO_HOST} - PASSPHRASE=${PASSPHRASE} - ENABLE_TUNNEL=${ENABLE_TUNNEL} - PORT=${PORT} ports: - "${PORT}:${PORT}" links: - mongo volumes: - ~/.outerbridge:/root/.outerbridge command: /bin/sh -c "sleep 3; outerbridge start" ================================================ FILE: package.json ================================================ { "name": "outerbridge", "version": "1.0.20", "private": true, "homepage": "https://outerbridge.io", "workspaces": [ "packages/*", "outerbridge", "ui", "components" ], "scripts": { "build": "turbo run build", "dev": "turbo run dev --parallel", "start": "run-script-os", "start:windows": "cd packages/server/bin && run start", "start:default": "cd packages/server/bin && ./run start", "clean": "npm exec -ws -- rimraf dist build", "format": "prettier --write \"**/*.{ts,tsx,md}\"", "test": "turbo run test", "lint": "eslint \"**/*.{js,jsx,ts,tsx,json,md}\"", "lint-fix": "yarn lint --fix", "quick": "pretty-quick --staged", "postinstall": "husky install" }, "lint-staged": { "*.{js,jsx,ts,tsx,json,md}": "eslint --fix" }, "devDependencies": { "turbo": "1.7.4", "@babel/preset-env": "^7.19.4", "@babel/preset-typescript": "7.18.6", "@types/express": "^4.17.13", "@typescript-eslint/typescript-estree": "^5.39.0", "eslint": "^8.24.0", "eslint-config-prettier": "^8.3.0", "eslint-config-react-app": "^7.0.1", "eslint-plugin-jsx-a11y": "^6.6.1", "eslint-plugin-markdown": "^3.0.0", "eslint-plugin-prettier": "^3.4.0", "eslint-plugin-react": "^7.26.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-unused-imports": "^2.0.0", "husky": "^8.0.1", "lint-staged": "^13.0.3", "prettier": "^2.7.1", "pretty-quick": "^3.1.3", "rimraf": "^3.0.2", "run-script-os": "^1.1.6", "typescript": "^4.8.4" }, "engines": { "node": ">=14.7.0" } } ================================================ FILE: packages/components/README.md ================================================ # Outerbridge Components Apps integration for Outerbridge. Contain Nodes and Credentials. ![Outerbridge](https://raw.githubusercontent.com/Outerbridgeio/Outerbridge/master/assets/outerbridge_brand.png) Install: ```bash npm i outerbridge-components ``` ## License Source code in this repository is made available under the [Apache License Version 2.0](https://github.com/Outerbridgeio/Outerbridge/blob/master/LICENSE.md). ================================================ FILE: packages/components/credentials/Alchemy/AlchemyApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class AlchemyApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'alchemyApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '', description: 'Navigate to the Dashboard page to copy your "API Key".' }, { label: 'Webhook Auth Token', name: 'authToken', type: 'string', default: '', optional: true, description: 'Navigate to the top right corner of Notify page to copy your "Auth Token".' } ] } } module.exports = { credClass: AlchemyApi } ================================================ FILE: packages/components/credentials/Arbiscan/ArbiscanApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class ArbiscanApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'arbiscanApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' } ] } } module.exports = { credClass: ArbiscanApi } ================================================ FILE: packages/components/credentials/Binance/BinanceApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class BinanceApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'binanceApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' }, { label: 'Secret Key', name: 'secretKey', type: 'password', default: '' } ] } } module.exports = { credClass: BinanceApi } ================================================ FILE: packages/components/credentials/Bscscan/BscscanApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class BscscanApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'bscscanApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' } ] } } module.exports = { credClass: BscscanApi } ================================================ FILE: packages/components/credentials/Celoscan/CeloscanApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class CeloscanApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'celoscanApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' } ] } } module.exports = { credClass: CeloscanApi } ================================================ FILE: packages/components/credentials/Cronosscan/CronosscanApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class CronosscanApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'cronosscanApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' } ] } } module.exports = { credClass: CronosscanApi } ================================================ FILE: packages/components/credentials/EmailSend/EmailSendSmtp.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class EmailSendSmtp implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'emailSendSmtp' this.version = 1.0 this.credentials = [ { label: 'User', name: 'user', type: 'string', default: '' }, { label: 'Password', name: 'password', type: 'password', default: '' }, { label: 'Host', name: 'host', type: 'string', default: '' }, { label: 'Port', name: 'port', type: 'number', default: 465 }, { label: 'SSL/TLS', name: 'secure', type: 'boolean', default: true } ] } } module.exports = { credClass: EmailSendSmtp } ================================================ FILE: packages/components/credentials/Etherscan/EtherscanApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class EtherscanApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'etherscanApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' } ] } } module.exports = { credClass: EtherscanApi } ================================================ FILE: packages/components/credentials/Fantomscan/FantomscanApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class FantomscanApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'fantomscanApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' } ] } } module.exports = { credClass: FantomscanApi } ================================================ FILE: packages/components/credentials/FootprintAnalytics/FootprintAnalyticsApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class FootprintAnalyticsApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'footprintAnalyticsApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' } ] } } module.exports = { credClass: FootprintAnalyticsApi } ================================================ FILE: packages/components/credentials/GitHub/GitHubApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class GitHubApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'gitHubApi' this.version = 1.0 this.credentials = [ { label: 'Access Token', name: 'accessToken', type: 'string', default: '', description: 'Register GitHub and get your access token."' } ] } } module.exports = { credClass: GitHubApi } ================================================ FILE: packages/components/credentials/Gnosisscan/GnosisscanApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class GnosisscanApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'gnosisscanApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' } ] } } module.exports = { credClass: GnosisscanApi } ================================================ FILE: packages/components/credentials/GoogleDocs/GoogleDocsOAuth2.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class GoogleDocsOAuth2 implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'googleDocsOAuth2Api' this.version = 1.0 this.credentials = [ { label: 'Client ID', name: 'clientID', type: 'string', default: '', description: 'How to get Client ID & Secret: https://www.youtube.com/watch?v=VBwDwHbPaYQ' }, { label: 'Client Secret', name: 'clientSecret', type: 'password', default: '', description: 'How to get Client ID & Secret: https://www.youtube.com/watch?v=VBwDwHbPaYQ' }, { label: 'Authorization URL', name: 'authUrl', type: 'string', default: 'https://accounts.google.com/o/oauth2/v2/auth' }, { label: 'Access Token URL', name: 'accessTokenUrl', type: 'string', default: 'https://oauth2.googleapis.com/token' }, { label: 'Authorization URL Parameters', name: 'authorizationURLParameters', type: 'string', default: 'access_type=offline&prompt=consent&response_type=code' }, { label: 'Scope', name: 'scope', type: 'json', default: `["https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/documents"]` } ] } } module.exports = { credClass: GoogleDocsOAuth2 } ================================================ FILE: packages/components/credentials/GoogleSheet/GoogleSheetOAuth2.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class GoogleSheetOAuth2 implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'googleSheetsOAuth2Api' this.version = 1.0 this.credentials = [ { label: 'Client ID', name: 'clientID', type: 'string', default: '', description: 'How to get Client ID & Secret: https://www.youtube.com/watch?v=VBwDwHbPaYQ' }, { label: 'Client Secret', name: 'clientSecret', type: 'password', default: '', description: 'How to get Client ID & Secret: https://www.youtube.com/watch?v=VBwDwHbPaYQ' }, { label: 'Authorization URL', name: 'authUrl', type: 'string', default: 'https://accounts.google.com/o/oauth2/v2/auth' }, { label: 'Access Token URL', name: 'accessTokenUrl', type: 'string', default: 'https://oauth2.googleapis.com/token' }, { label: 'Authorization URL Parameters', name: 'authorizationURLParameters', type: 'string', default: 'access_type=offline&prompt=consent&response_type=code' }, { label: 'Scope', name: 'scope', type: 'json', default: `["https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/spreadsheets"]` } ] } } module.exports = { credClass: GoogleSheetOAuth2 } ================================================ FILE: packages/components/credentials/HTTP/HTTPBasicAuth.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class HTTPBasicAuth implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'httpBasicAuth' this.version = 1.0 this.credentials = [ { label: 'Username', name: 'userName', type: 'string', default: '' }, { label: 'Password', name: 'password', type: 'password', default: '' } ] } } module.exports = { credClass: HTTPBasicAuth } ================================================ FILE: packages/components/credentials/HTTP/HTTPBearerTokenAuth.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class HTTPBearerTokenAuth implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'httpBearerTokenAuth' this.version = 1.0 this.credentials = [ { label: 'Token', name: 'token', type: 'string', default: '' } ] } } module.exports = { credClass: HTTPBearerTokenAuth } ================================================ FILE: packages/components/credentials/Helio/HelioApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class HelioApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'helioApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' }, { label: 'Secret Key', name: 'secretKey', type: 'password', default: '' } ] } } module.exports = { credClass: HelioApi } ================================================ FILE: packages/components/credentials/Helius/HeliusApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class HeliusApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'heliusApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' } ] } } module.exports = { credClass: HeliusApi } ================================================ FILE: packages/components/credentials/Hubspot/HubspotApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class HubspotApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'hubspotCredential' this.version = 1.0 this.credentials = [ { label: 'Private App Access Token', name: 'accessToken', type: 'string', description: `Private apps allow you to use HubSpot's APIs to access specific data from your HubSpot account. Learn how to create one here`, default: '' } ] } } module.exports = { credClass: HubspotApi } ================================================ FILE: packages/components/credentials/HuggingFace/HuggingFaceAccessToken.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class HuggingFaceAccessToken implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'huggingFaceAccessToken' this.version = 1.0 this.credentials = [ { label: 'Access Token', name: 'accessToken', type: 'string', default: '', description: 'Navigate to the Access Token page and copy your token' } ] } } module.exports = { credClass: HuggingFaceAccessToken } ================================================ FILE: packages/components/credentials/Imap/Imap.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class Imap implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'imap' this.version = 1.0 this.credentials = [ { label: 'User Email', name: 'userEmail', type: 'string', default: '' }, { label: 'Password', name: 'password', type: 'password', default: '' }, { label: 'Host', name: 'host', type: 'string', default: '' }, { label: 'Port', name: 'port', type: 'number', default: 993 }, { label: 'Enable TLS', name: 'tls', type: 'boolean', default: true } ] } } module.exports = { credClass: Imap } ================================================ FILE: packages/components/credentials/Infura/InfuraApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class InfuraApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'infuraApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' }, { label: 'Secret Key', name: 'secretKey', type: 'password', default: '' } ] } } module.exports = { credClass: InfuraApi } ================================================ FILE: packages/components/credentials/Mailchimp/MailchimpApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class MailchimpApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'mailChimpCredential' this.version = 1.0 this.credentials = [ { label: 'Mailchimp API Key', name: 'apiKey', type: 'string', default: '' } ] } } module.exports = { credClass: MailchimpApi } ================================================ FILE: packages/components/credentials/MoonBeamScan/MoonBeamScanApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class MoonBeamScanApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'moonBeamScanApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' } ] } } module.exports = { credClass: MoonBeamScanApi } ================================================ FILE: packages/components/credentials/MoonRiverScan/MoonRiverScanApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class MoonRiverScanApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'moonRiverScanApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' } ] } } module.exports = { credClass: MoonRiverScanApi } ================================================ FILE: packages/components/credentials/Moralis/MoralisApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class MoralisApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'moralisApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '', description: 'How to get API key: https://docs.moralis.io/reference/getting-the-api-key' } ] } } module.exports = { credClass: MoralisApi } ================================================ FILE: packages/components/credentials/Notion/NotionApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class NotionApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'notionApi' this.version = 1.0 this.credentials = [ { label: 'Internal Integration Token', name: 'integrationToken', type: 'string', default: '', description: 'Get your Internal Integration Token for your workspace' } ] } } module.exports = { credClass: NotionApi } ================================================ FILE: packages/components/credentials/OpenAI/OpenAIApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class OpenAIApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'openAIApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '', description: 'Navigate to the API page to copy your "API Key".' } ] } } module.exports = { credClass: OpenAIApi } ================================================ FILE: packages/components/credentials/Opensea/OpenseaApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class OpenseaApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'openSeaApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' } ] } } module.exports = { credClass: OpenseaApi } ================================================ FILE: packages/components/credentials/OptimisticEtherscan/OptimisticEtherscanApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class OptimisticEtherscanApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'optimisticEtherscanApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' } ] } } module.exports = { credClass: OptimisticEtherscanApi } ================================================ FILE: packages/components/credentials/Pinata/PinataApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class PinataApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'pinataApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' }, { label: 'Secret Key', name: 'secretKey', type: 'password', default: '' } ] } } module.exports = { credClass: PinataApi } ================================================ FILE: packages/components/credentials/Polygonscan/PolygonscanApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class PolygonscanApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'polygonscanApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' } ] } } module.exports = { credClass: PolygonscanApi } ================================================ FILE: packages/components/credentials/QuickNode/QuickNodeEndpoints.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class QuickNodeEndpoints implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'quickNodeEndpoints' this.version = 1.0 this.credentials = [ { label: 'HTTP Provider', name: 'httpProvider', type: 'string', default: '', placeholder: 'https://sample-endpoint-name.network.discover.quiknode.pro/token-goes-here/' }, { label: 'WSS Provider', name: 'wssProvider', type: 'string', default: '', placeholder: 'wss://sample-endpoint-name.network.discover.quiknode.pro/token-goes-here/', optional: true } ] } } module.exports = { credClass: QuickNodeEndpoints } ================================================ FILE: packages/components/credentials/RequestFinanceApi/RequestFinanceApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class RequestFinanceApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'requestFinanceApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' } ] } } module.exports = { credClass: RequestFinanceApi } ================================================ FILE: packages/components/credentials/SnowTrace/SnowTraceApi.ts ================================================ import { INodeCredential, INodeParams } from '../../src/Interface' class SnowTraceApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'snowtraceApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' } ] } } module.exports = { credClass: SnowTraceApi } ================================================ FILE: packages/components/credentials/Telegram/TelegramApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class TelegramApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'telegramApi' this.version = 1.0 this.credentials = [ { label: 'Bot Token', name: 'botToken', type: 'string', placeholder: 'eg: 1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHI', default: '', description: 'Telegram bot token. Learn how to get it' } ] } } module.exports = { credClass: TelegramApi } ================================================ FILE: packages/components/credentials/Twitter/TwitterApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class TwitterApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'twitterApi' this.version = 1.0 this.credentials = [ { label: 'Bearer Token', name: 'bearerToken', type: 'string', default: '', description: 'Register Twitter Dev account and get your token."' } ] } } module.exports = { credClass: TwitterApi } ================================================ FILE: packages/components/credentials/Typeform/TypeformApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class TypeformApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'typeformApi' this.version = 1.0 this.credentials = [ { label: 'API Key', name: 'apiKey', type: 'string', default: '' } ] } } module.exports = { credClass: TypeformApi } ================================================ FILE: packages/components/credentials/TypeformWebhook/TypeformApi.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class TypeformApi implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'typeformApi' this.version = 1.0 this.credentials = [ { label: 'Typeform Access Token', name: 'accessToken', type: 'string', default: '', description: 'Get your access token from typeform account section' } ] } } module.exports = { credClass: TypeformApi } ================================================ FILE: packages/components/credentials/Xero/XeroOAuth2.ts ================================================ import { INodeParams, INodeCredential } from '../../src/Interface' class XeroOAuth2 implements INodeCredential { name: string version: number credentials: INodeParams[] constructor() { this.name = 'xeroOAuth2Api' this.version = 1.0 this.credentials = [ { label: 'Client ID', name: 'clientID', type: 'string', default: '', description: 'How to get Client ID & Secret: https://developer.xero.com/documentation/guides/oauth2/client-credentials/' }, { label: 'Client Secret', name: 'clientSecret', type: 'password', default: '', description: 'How to get Client ID & Secret: https://developer.xero.com/documentation/guides/oauth2/client-credentials/' }, { label: 'Authorization URL', name: 'authUrl', type: 'string', default: 'https://login.xero.com/identity/connect/authorize' }, { label: 'Access Token Url', name: 'accessTokenUrl', type: 'string', default: 'https://identity.xero.com/connect/token' }, { label: 'Authorization URL Parameters', name: 'authorizationURLParameters', type: 'string', default: 'response_type=code' }, { label: 'Scope', name: 'scope', type: 'json', default: `[ "openid", "profile", "email", "accounting.settings", "accounting.reports.read", "accounting.journals.read", "accounting.contacts", "accounting.attachments", "accounting.transactions", "offline_access" ]` } ] } } module.exports = { credClass: XeroOAuth2 } ================================================ FILE: packages/components/gulpfile.ts ================================================ import gulp from 'gulp' const { src, dest } = gulp function copyIcons() { return src(['nodes/**/*.{jpg,png,svg}']).pipe(dest('dist/nodes')) } exports.default = copyIcons ================================================ FILE: packages/components/nodes/Alchemy/Alchemy.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' import { alchemyHTTPAPIs, NETWORK } from '../../src/ChainNetwork' import { alchemySupportedNetworks, ethOperations, IETHOperation, operationCategoryMapping, polygonOperations } from '../../src/ETHOperations' import { getContractsForOwnerProperties, getNFTMetadataProperties, getNFTsForCollectionProperties, getNFTsProperties, getOwnersForCollectionProperties, getOwnersForTokenProperties, isHolderOfCollectionProperties, NFTOperationsOptions, searchContractMetadataProperties, tokenAPIOperations, transactionReceiptsOperations } from './extendedOperation' import axios, { AxiosRequestConfig, Method } from 'axios' import { solanaAPIOperations, solanaOperationsNetworks } from './solanaOperation' import { AlchemySupportedNetworks } from './supportedNetwork' class Alchemy implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number credentials?: INodeParams[] networks?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Alchemy' this.name = 'alchemy' this.icon = 'alchemy.svg' this.type = 'action' this.category = 'Network Provider' this.version = 1.1 this.description = 'Perform Alchemy on-chain operations' this.incoming = 1 this.outgoing = 1 this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...AlchemySupportedNetworks] } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Alchemy API Key', name: 'alchemyApi' } ], default: 'alchemyApi' } ] as INodeParams[] this.inputParameters = [ { label: 'API', name: 'api', type: 'options', options: [ { label: 'EVM Chain API', name: 'chainAPI', description: 'API for fetching standard EVM onchain data using Alchemy supported calls', show: { 'networks.network': alchemySupportedNetworks } }, { label: 'NFT API', name: 'nftAPI', description: 'API for fetching NFT data, including ownership, metadata attributes, and more.', show: { 'networks.network': [NETWORK.MAINNET, NETWORK.GÖRLI, NETWORK.MATIC, NETWORK.MATIC_MUMBAI] } }, { label: 'Transaction Receipts API', name: 'txReceiptsAPI', description: 'API that gets all transaction receipts for a given block by number or block hash.', show: { 'networks.network': [ NETWORK.MAINNET, NETWORK.GÖRLI, NETWORK.MATIC, NETWORK.MATIC_MUMBAI, NETWORK.ARBITRUM, NETWORK.ARBITRUM_GOERLI ] } }, { label: 'Token API', name: 'tokenAPI', description: 'The Token API allows you to easily get token information, minimizing the number of necessary requests.', show: { 'networks.network': alchemySupportedNetworks } }, { label: 'Solana API', name: 'solanaAPI', description: 'API for fetching Solana on-chain data using Alchemy supported calls', show: { 'networks.network': solanaOperationsNetworks } } ], default: 'chainAPI' }, { label: 'Chain Category', name: 'chainCategory', type: 'options', options: [ { label: 'Retrieving Blocks', name: 'retrievingBlocks', description: 'Retrieve onchain blocks data' }, { label: 'EVM/Smart Contract Execution', name: 'evmExecution', description: 'Execute or submit transaction onto blockchain' }, { label: 'Reading Transactions', name: 'readingTransactions', description: 'Read onchain transactions data' }, { label: 'Account Information', name: 'accountInformation', description: 'Retrieve onchain account information' }, { label: 'Event Logs', name: 'eventLogs', description: 'Fetch onchain logs' }, { label: 'Chain Information', name: 'chainInformation', description: 'Get general selected blockchain information' }, { label: 'Retrieving Uncles', name: 'retrievingUncles', description: 'Retrieve onchain uncles blocks data' }, { label: 'Filters', name: 'filters', description: 'Get block filters and logs, or create new filter' } ], show: { 'inputParameters.api': ['chainAPI'] } }, { label: 'Chain Category', name: 'chainCategory', type: 'options', options: [ { label: 'Reading & Writing Transactions', name: 'readWriteTransactions', description: 'Read and Write transactins onto Solana chain' }, { label: 'Getting Blocks', name: 'gettingBlocks', description: 'Get Solana blocks data' }, { label: 'Account Information', name: 'accountInformation', description: 'Retrieve Solana onchain account information' }, { label: 'Network Information', name: 'networkInformation', description: 'Get Solana network onchain information' }, { label: 'Slot Information', name: 'slotInformation', description: 'Fetch Solana slot information' }, { label: 'Node Information', name: 'nodeInformation', description: 'Retrieve Solana node onchain information' }, { label: 'Token Information', name: 'tokenInformation', description: 'Fetch Solana onchain token information' }, { label: 'Network Inflation', name: 'networkInflation', description: 'Retrieve Solana network inflation onchain data' } ], show: { 'inputParameters.api': ['solanaAPI'] } }, { label: 'Operation', name: 'operation', type: 'asyncOptions', loadMethod: 'getOperations' }, ...getNFTsProperties, ...getNFTMetadataProperties, ...getNFTsForCollectionProperties, ...getOwnersForCollectionProperties, ...getOwnersForTokenProperties, ...searchContractMetadataProperties, ...isHolderOfCollectionProperties, ...getContractsForOwnerProperties, { label: 'Parameters', name: 'parameters', type: 'json', placeholder: '["param1", "param2"]', optional: true, description: 'Operation parameters in array. Ex: ["param1", "param2"]', show: { 'inputParameters.api': ['chainAPI', 'txReceiptsAPI', 'tokenAPI', 'solanaAPI'] } } ] as INodeParams[] } loadMethods = { async getOperations(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const inputParametersData = nodeData.inputParameters const networksData = nodeData.networks if (inputParametersData === undefined || networksData === undefined) { return returnData } const api = inputParametersData.api as string const chainCategory = inputParametersData.chainCategory as string const network = networksData.network as NETWORK if (api === 'chainAPI' || api === 'txReceiptsAPI' || api === 'tokenAPI' || api === 'solanaAPI') { const operations = getSelectedOperations(api, network).filter( (op: IETHOperation) => Object.prototype.hasOwnProperty.call(op.providerNetworks, 'alchemy') && op.providerNetworks['alchemy'].includes(network) ) if (api === 'chainAPI' && !chainCategory) return returnData if (api === 'solanaAPI' && !chainCategory) return returnData let filteredOperations: IETHOperation[] = operations if (api === 'chainAPI' || api === 'solanaAPI') filteredOperations = operations.filter( (op: IETHOperation) => op.parentGroup === operationCategoryMapping[chainCategory] ) for (const op of filteredOperations) { returnData.push({ label: op.name, name: op.value, parentGroup: op.parentGroup, description: op.description, inputParameters: op.inputParameters, exampleParameters: op.exampleParameters, exampleResponse: op.exampleResponse }) } return returnData } else if (api === 'nftAPI') { return NFTOperationsOptions } else { return returnData } } } async run(nodeData: INodeData): Promise { const networksData = nodeData.networks const inputParametersData = nodeData.inputParameters const credentials = nodeData.credentials if (inputParametersData === undefined || networksData === undefined) { throw new Error('Required data missing') } if (credentials === undefined) { throw new Error('Missing credentials') } // GET api const api = inputParametersData.api as string // GET network const network = networksData.network as NETWORK // GET credentials const apiKey = credentials.apiKey as string // GET operation const operation = inputParametersData.operation as string if (api === 'chainAPI' || api === 'txReceiptsAPI' || api === 'tokenAPI' || api === 'solanaAPI') { const uri = `${alchemyHTTPAPIs[network]}${apiKey}` let responseData: any // tslint:disable-line: no-any let bodyParameters: any[] = [] // tslint:disable-line: no-any const returnData: ICommonObject[] = [] const parameters = inputParametersData.parameters as string if (parameters) { try { bodyParameters = JSON.parse(parameters.replace(/\s/g, '')) } catch (error) { throw handleErrorMessage(error) } } try { let totalOperations: IETHOperation[] = [] totalOperations = getSelectedOperations(api, network) const result = totalOperations.find((obj) => { return obj.value === operation }) if (result === undefined) throw new Error('Invalid Operation') const requestBody = JSON.parse(JSON.stringify(result.body)) const bodyParams = requestBody.params requestBody.params = Array.isArray(bodyParameters) ? bodyParameters.concat(bodyParams) : bodyParameters const axiosConfig: AxiosRequestConfig = { method: result.method as Method, url: uri, data: requestBody, headers: { 'Content-Type': 'application/json' } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) returnData.push(...responseData) else returnData.push(responseData) return returnNodeExecutionData(returnData) } //NFT API else if (api === 'nftAPI') { const uri = `${alchemyHTTPAPIs[network]}${apiKey}/${operation}/` let responseData: any // tslint:disable-line: no-any const queryParameters: ICommonObject = {} const returnData: ICommonObject[] = [] if (operation === 'getNFTs') { const owner = inputParametersData.owner as string const pageKey = inputParametersData.pageKey as string const withMetadata = inputParametersData.withMetadata as boolean queryParameters['owner'] = owner queryParameters['withMetadata'] = withMetadata if (pageKey) queryParameters['pageKey'] = pageKey } else if (operation === 'getNFTMetadata') { const contractAddress = inputParametersData.contractAddress as string const tokenId = inputParametersData.tokenId as string const tokenType = inputParametersData.tokenType as string queryParameters['contractAddress'] = contractAddress queryParameters['tokenId'] = tokenId if (tokenType) queryParameters['tokenType'] = tokenType } else if (operation === 'getNFTsForCollection') { const contractAddress = inputParametersData.contractAddress as string const startToken = inputParametersData.startToken as string const withMetadata = inputParametersData.withMetadata as boolean const limit = inputParametersData.limit as number const tokenUriTimeoutInMs = inputParametersData.tokenUriTimeoutInMs as number queryParameters['contractAddress'] = contractAddress if (startToken) queryParameters['startToken'] = startToken if (withMetadata) queryParameters['withMetadata'] = withMetadata if (limit) queryParameters['limit'] = limit if (tokenUriTimeoutInMs) queryParameters['tokenUriTimeoutInMs'] = tokenUriTimeoutInMs } else if (operation === 'getOwnersForCollection') { const contractAddress = inputParametersData.contractAddress as string const withTokenBalances = inputParametersData.withTokenBalances as boolean const block = inputParametersData.block as string const pageKey = inputParametersData.pageKey as string queryParameters['contractAddress'] = contractAddress if (withTokenBalances) queryParameters['withTokenBalances'] = withTokenBalances if (block) queryParameters['block'] = block if (pageKey) queryParameters['pageKey'] = pageKey } else if (operation === 'getOwnersForToken' || operation === 'computeRarity') { const contractAddress = inputParametersData.contractAddress as string const tokenId = inputParametersData.tokenId as string queryParameters['contractAddress'] = contractAddress queryParameters['tokenId'] = tokenId } else if ( operation === 'isSpamContract' || operation === 'reingestContract' || operation === 'getFloorPrice' || operation === 'summarizeNFTAttributes' || operation === 'reportSpamContract' ) { const contractAddress = inputParametersData.contractAddress as string queryParameters['contractAddress'] = contractAddress } else if (operation === 'searchContractMetadata') { const query = inputParametersData.query as string queryParameters['query'] = query } else if (operation === 'isHolderOfCollection') { const contractAddress = inputParametersData.contractAddress as string const wallet = inputParametersData.wallet as string queryParameters['contractAddress'] = contractAddress queryParameters['wallet'] = wallet } else if (operation === 'getNFTSales') { const contractAddress = inputParametersData.contractAddress as string const tokenId = inputParametersData.tokenId as string const startBlock = inputParametersData.startBlock as string const startLogIndex = inputParametersData.startLogIndex as number const startBundleIndex = inputParametersData.startBundleIndex as number const ascendingOrder = inputParametersData.ascendingOrder as boolean const marketplace = inputParametersData.marketplace as string const buyerAddress = inputParametersData.buyerAddress as string const sellerAddress = inputParametersData.sellerAddress as string const buyerIsMaker = inputParametersData.buyerIsMaker as boolean const limit = inputParametersData.limit as number queryParameters['contractAddress'] = contractAddress queryParameters['tokenId'] = tokenId if (startBlock) queryParameters['startBlock'] = startBlock if (startLogIndex) queryParameters['startLogIndex'] = startLogIndex if (startBundleIndex) queryParameters['startBundleIndex'] = startBundleIndex if (ascendingOrder) queryParameters['ascendingOrder'] = ascendingOrder if (marketplace) queryParameters['marketplace'] = marketplace if (buyerAddress) queryParameters['buyerAddress'] = buyerAddress if (sellerAddress) queryParameters['sellerAddress'] = sellerAddress if (buyerIsMaker) queryParameters['buyerIsMaker'] = buyerIsMaker if (limit) queryParameters['limit'] = limit } else if (operation === 'getContractsForOwner') { const owner = inputParametersData.owner as string const pageKey = inputParametersData.pageKey as string queryParameters['owner'] = owner if (pageKey) queryParameters['pageKey'] = pageKey } try { const axiosConfig: AxiosRequestConfig = { method: 'GET', url: uri, params: queryParameters, paramsSerializer: (params) => serializeQueryParams(params), headers: { 'Content-Type': 'application/json' } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) returnData.push(...responseData) else returnData.push(responseData) return returnNodeExecutionData(returnData) } return returnNodeExecutionData([]) } } const getSelectedOperations = (api: string, network: string) => { switch (api) { case 'chainAPI': if (network === NETWORK.MATIC || network === NETWORK.MATIC_MUMBAI) return [...polygonOperations, ...ethOperations] else return ethOperations case 'txReceiptsAPI': return transactionReceiptsOperations case 'tokenAPI': return tokenAPIOperations case 'solanaAPI': return solanaAPIOperations default: return ethOperations } } module.exports = { nodeClass: Alchemy } ================================================ FILE: packages/components/nodes/Alchemy/AlchemyTrigger.ts ================================================ import { INode, INodeData, INodeOptionsValue, INodeParams, IProviders, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import EventEmitter from 'events' import { alchemyWSSAPIs, ArbitrumNetworks, ETHNetworks, NETWORK, OptimismNetworks, PolygonNetworks, SolanaNetworks } from '../../src/ChainNetwork' import { subscribeOperations, unsubscribeOperations } from './subscribeOperation' import { IETHOperation } from '../../src/ETHOperations' import WebSocket from 'ws' class AlchemyTrigger extends EventEmitter implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] providers: IProviders constructor() { super() this.label = 'Alchemy Trigger' this.name = 'alchemyTrigger' this.icon = 'alchemy.svg' this.type = 'trigger' this.category = 'Network Provider' this.version = 1.0 this.description = 'Start workflow whenever subscribed event happened' this.incoming = 0 this.outgoing = 1 this.providers = {} this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...ETHNetworks, ...PolygonNetworks, ...ArbitrumNetworks, ...OptimismNetworks, ...SolanaNetworks] } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Alchemy API Key', name: 'alchemyApi' } ], default: 'alchemyApi' } ] as INodeParams[] this.inputParameters = [ { label: 'Subscribe Operation', name: 'subscribeOperation', type: 'asyncOptions', loadMethod: 'getSubscribeOperations' }, { label: 'Parameters', name: 'parameters', type: 'json', placeholder: `[ "param1", "param2" ]`, optional: true, description: 'Operation parameters in array. Ex: ["param1", "param2"]' }, { label: 'Unsubscribe Operation', name: 'unsubscribeOperation', type: 'asyncOptions', loadMethod: 'getUnsubscribeOperations' } ] as INodeParams[] } loadMethods = { async getSubscribeOperations(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) { return returnData } const network = networksData.network as NETWORK const totalOperations = subscribeOperations const filteredOperations = totalOperations.filter( (op: IETHOperation) => Object.prototype.hasOwnProperty.call(op.providerNetworks, 'alchemy') && op.providerNetworks['alchemy'].includes(network) ) for (const op of filteredOperations) { returnData.push({ label: op.name, name: op.value, parentGroup: op.parentGroup, description: op.description, inputParameters: op.inputParameters, exampleParameters: op.exampleParameters, exampleResponse: op.exampleResponse }) } return returnData }, async getUnsubscribeOperations(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) { return returnData } const network = networksData.network as NETWORK const totalOperations = unsubscribeOperations const filteredOperations = totalOperations.filter( (op: IETHOperation) => Object.prototype.hasOwnProperty.call(op.providerNetworks, 'alchemy') && op.providerNetworks['alchemy'].includes(network) ) for (const op of filteredOperations) { returnData.push({ label: op.name, name: op.value, parentGroup: op.parentGroup, description: op.description, inputParameters: op.inputParameters, exampleParameters: op.exampleParameters, exampleResponse: op.exampleResponse }) } return returnData } } async runTrigger(nodeData: INodeData): Promise { const networksData = nodeData.networks const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (networksData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } if (credentials === undefined) { throw new Error('Missing credentials') } // GET network const network = networksData.network as NETWORK // GET credentials const apiKey = credentials.apiKey as string const wssProvider = `${alchemyWSSAPIs[network]}${apiKey}` // GET subscribeOperation const subscribeOperation = inputParametersData.subscribeOperation as string // GET parameters let bodyParameters: any const parameters = inputParametersData.parameters as string if (parameters) { try { bodyParameters = JSON.parse(parameters.replace(/\s/g, '')) } catch (error) { throw handleErrorMessage(error) } } const emitEventKey = nodeData.emitEventKey as string const result = subscribeOperations.find((obj) => { return obj.value === subscribeOperation }) if (result === undefined) throw new Error('Invalid Operation') const requestBody = result.body requestBody.params = bodyParameters const ws = new WebSocket(wssProvider) ws.on('open', function open() { ws.send(JSON.stringify(requestBody)) }) let subscriptionID = '' ws.on('message', (data) => { const messageData = JSON.parse(data as any) if (messageData.method) { this.emit(emitEventKey, returnNodeExecutionData(messageData)) } else { subscriptionID = messageData.result this.providers[emitEventKey] = { provider: ws, filter: subscriptionID } } }) } async removeTrigger(nodeData: INodeData): Promise { const emitEventKey = nodeData.emitEventKey as string const inputParametersData = nodeData.inputParameters if (inputParametersData === undefined) { throw new Error('Required data missing') } if (Object.prototype.hasOwnProperty.call(this.providers, emitEventKey)) { const provider: WebSocket = this.providers[emitEventKey].provider const subscriptionID = this.providers[emitEventKey].filter const result = unsubscribeOperations.find((obj) => { return obj.value === (inputParametersData.unsubscribeOperation as string) }) if (result === undefined) throw new Error('Invalid Operation') const requestBody = result.body requestBody.params = [subscriptionID] provider.send(JSON.stringify(requestBody)) provider.close(1000) this.removeAllListeners(emitEventKey) } } } module.exports = { nodeClass: AlchemyTrigger } ================================================ FILE: packages/components/nodes/Alchemy/AlchemyWebhook.ts ================================================ import { ICommonObject, INode, INodeData, INodeParams, IWebhookNodeExecutionData, NodeType } from '../../src/Interface' import { returnWebhookNodeExecutionData } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' class AlchemyWebhook implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions: INodeParams[] networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Alchemy Webhook' this.name = 'AlchemyWebhook' this.icon = 'alchemy.svg' this.type = 'webhook' this.category = 'Network Provider' this.version = 1.0 this.description = 'Start workflow whenever Alchemy webhook event happened' this.incoming = 0 this.outgoing = 1 this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [ { label: 'Mainnet', name: 'ETH_MAINNET' }, { label: 'Goerli', name: 'ETH_GOERLI' }, { label: 'Polygon Mainnet', name: 'MATIC_MAINNET' }, { label: 'Polygon Mumbai', name: 'MATIC_MUMBAI' }, { label: 'Arbitrum Mainnet', name: 'ARB_MAINNET' }, { label: 'Arbitrum Goerli', name: 'ARB_GOERLI' }, { label: 'Optimism Mainnet', name: 'OPT_MAINNET' }, { label: 'Optimism Goerli', name: 'OPT_GOERLI' } ], default: 'ETH_MAINNET' } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Alchemy API Key', name: 'alchemyApi' } ], default: 'alchemyApi' } ] as INodeParams[] this.actions = [ { label: 'Event', name: 'webhook_type', type: 'options', options: [ { label: 'Mined Transactions', name: 'MINED_TRANSACTION', description: 'Triggered anytime a transaction sent through your API key gets successfully mined.' }, { label: 'Dropped Transactions', name: 'DROPPED_TRANSACTION', description: `The Dropped Transactions Webhook is used to notify your app anytime a transaction send through your API key gets dropped.` }, { label: 'Address Activity', name: 'ADDRESS_ACTIVITY', description: `The Address Activity Webhook allows you to track all ETH, ERC20 and ERC721 transfer events for as many Ethereum addresses as you'd like.` } ], default: 'MINED_TRANSACTION' } ] as INodeParams[] this.inputParameters = [ { label: 'App ID', name: 'app_id', type: 'string', default: '', description: 'App ID can be found within the URL of your specific app. For example, given the URL https://dashboard.alchemyapi.io/apps/xfu8frt3wf94j7h5 your App ID would be xfu8frt3wf94j7h5', show: { 'actions.webhook_type': ['MINED_TRANSACTION', 'DROPPED_TRANSACTION'] } }, { label: 'Ethereum Addresses', name: 'addresses', type: 'string', default: '', description: 'Ethereum addresses to track the transfer events', placeholder: '[""]', show: { 'actions.webhook_type': ['ADDRESS_ACTIVITY'] } } ] as INodeParams[] } webhookMethods = { async createWebhook(nodeData: INodeData, webhookFullUrl: string): Promise { // Check if webhook exists const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters const networksData = nodeData.networks const actionsData = nodeData.actions if (inputParametersData === undefined || actionsData === undefined || networksData === undefined) { throw new Error('Required data missing') } if (credentials === undefined) { throw new Error('Missing credentials') } const authToken = credentials.authToken as string const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://dashboard.alchemyapi.io/api/team-webhooks`, headers: { 'X-Alchemy-Token': authToken } } try { const response = await axios(axiosConfig) const responseData = response.data const webhooks = responseData.data const network = networksData.network as string const webhook_type = actionsData.webhook_type as string let webhookExist = false for (const webhook of webhooks) { if (webhook.webhook_type === webhook_type && webhook.webhook_url === webhookFullUrl) { if (webhook_type !== 'ADDRESS_ACTIVITY') { const app_id = (inputParametersData.app_id as string) || '' if (webhook.app_id === app_id) { webhookExist = true break } continue } webhookExist = true break } } if (!webhookExist) { const data: ICommonObject = { webhook_type, network, webhook_url: webhookFullUrl } if (webhook_type === 'ADDRESS_ACTIVITY') { let addresses = (inputParametersData.addresses as string) || '[]' //Remove whitespaces addresses = addresses.replace(/\s/g, '') if (addresses) data.addresses = JSON.parse(addresses) } else { const app_id = (inputParametersData.app_id as string) || '' data.app_id = app_id } const axiosCreateConfig: AxiosRequestConfig = { method: 'POST' as Method, url: `https://dashboard.alchemyapi.io/api/create-webhook`, data, headers: { 'X-Alchemy-Token': authToken } } let createResponseData = await axios(axiosCreateConfig) createResponseData = createResponseData.data if (createResponseData && createResponseData.data && createResponseData.data.id) { return createResponseData.data.id } return } } catch (error) { return } }, async deleteWebhook(nodeData: INodeData, webhookId: string): Promise { const credentials = nodeData.credentials if (credentials === undefined) { throw new Error('Missing credentials') } const authToken = credentials.authToken as string const axiosConfig: AxiosRequestConfig = { method: 'DELETE' as Method, url: `https://dashboard.alchemyapi.io/api/delete-webhook?webhook_id=${webhookId}`, headers: { 'X-Alchemy-Token': authToken } } try { await axios(axiosConfig) } catch (error) { return false } return true } } async runWebhook(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters const req = nodeData.req if (inputParametersData === undefined) { throw new Error('Required data missing') } if (req === undefined) { throw new Error('Missing request') } //TODO: Verify webhook via signing key const returnData: ICommonObject[] = [] returnData.push({ headers: req?.headers, params: req?.params, query: req?.query, body: req?.body, rawBody: (req as any).rawBody, url: req?.url }) return returnWebhookNodeExecutionData(returnData) } } module.exports = { nodeClass: AlchemyWebhook } ================================================ FILE: packages/components/nodes/Alchemy/extendedOperation.ts ================================================ import { INodeOptionsValue, INodeParams, NETWORK } from '../../src' import { IETHOperation } from '../../src/ETHOperations' export const NFTOperationsOptions = [ { label: 'Get NFTs', name: 'getNFTs', description: 'Gets all NFTs currently owned by a given address' }, { label: 'Get NFT Sales', name: 'getNFTSales', description: 'Gets NFT sales that have happened through on-chain marketplaces', show: { 'networks.network': [NETWORK.MAINNET] } }, { label: 'Get NFT Metadata', name: 'getNFTMetadata', description: 'Gets the metadata associated with a given NFT' }, { label: 'Get NFTs For Collection', name: 'getNFTsForCollection', description: 'Gets all NFTs for a given NFT contract' }, { label: 'Get Owners For Collection', name: 'getOwnersForCollection', description: 'Gets all owners for a given NFT contract.' }, { label: 'Get Owners For Token', name: 'getOwnersForToken', description: 'Get the owner(s) for a token.' }, { label: 'Get Contracts For Owner', name: 'getContractsForOwner', description: 'Gets all NFT contracts held by an owner address.' }, { label: 'Get Spam Contracts', name: 'getSpamContracts', description: 'Returns a list of all spam contracts marked by Alchemy.', show: { 'networks.network': [NETWORK.MAINNET] } }, { label: 'Is Spam Contracts', name: 'isSpamContract', description: 'Returns whether a contract is marked as spam or not by Alchemy.', show: { 'networks.network': [NETWORK.MAINNET] } }, { label: 'Reingest Contract', name: 'reingestContract', description: 'Triggers metadata refresh for an entire NFT collection and refreshes stale metadata after a collection reveal/collection changes.', show: { 'networks.network': [NETWORK.MAINNET] } }, { label: 'Get Floor Price', name: 'getFloorPrice', description: 'Returns the floor prices of a NFT collection by marketplace.', show: { 'networks.network': [NETWORK.MAINNET] } }, { label: 'Compute Rarity', name: 'computeRarity', description: 'Computes the rarity of each attribute of an NFT.', show: { 'networks.network': [NETWORK.MAINNET] } }, { label: 'Search Contract Metadata', name: 'searchContractMetadata', description: 'Search for a keyword across metadata of all ERC-721 and ERC-1155 smart contracts', show: { 'networks.network': [NETWORK.MAINNET] } }, { label: 'Summarize NFT Attributes', name: 'summarizeNFTAttributes', description: 'Generate a summary of attribute prevalence for an NFT collection.', show: { 'networks.network': [NETWORK.MAINNET] } }, { label: 'Is Holder Of Collection', name: 'isHolderOfCollection', description: 'Checks whether a wallet holds a NFT in a given collection' }, { label: 'Report Spam Contract', name: 'reportSpamContract', description: 'Report a particular contract address to our APIs if you think it is spam', show: { 'networks.network': [NETWORK.MAINNET] } } ] as INodeOptionsValue[] export const getNFTsProperties = [ { label: 'Owner Address', name: 'owner', type: 'string', description: 'Address for NFT owner (can be in ENS format!)', default: '', show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTs'] } }, { label: 'PageKey', name: 'pageKey', type: 'string', description: 'UUID for pagination. If more results are available, a UUID pageKey will be returned in the response. Pass that UUID into pageKey to fetch the next 100 NFTs.', default: '', show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTs'] }, optional: true }, { label: 'Metadata', name: 'withMetadata', type: 'boolean', description: 'If boolean is set to true the query will include metadata for each returned token.', default: true, show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTs'] } } ] as INodeParams[] export const getNFTMetadataProperties = [ { label: 'Contract Address', name: 'contractAddress', type: 'string', description: 'Address of NFT contract', default: '', show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTMetadata'] } }, { label: 'Token Id', name: 'tokenId', type: 'string', description: 'Id for NFT', default: '', show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTMetadata'] } }, { label: 'Token Type', name: 'tokenType ', type: 'options', description: '"ERC721" or "ERC1155"; specifies type of token to query for', options: [ { label: 'ERC721', name: 'ERC721' }, { label: 'ERC1155', name: 'ERC1155' }, { label: '', name: '' } ], default: '', show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTMetadata'] } } ] as INodeParams[] export const getNFTsForCollectionProperties = [ { label: 'Contract Address', name: 'contractAddress', type: 'string', description: 'Contract address for the NFT collection', default: '', show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTsForCollection'] } }, { label: 'Start Token', name: 'startToken', type: 'string', description: 'An offset used for pagination. Can be a hex string, or a decimal.', default: '', optional: true, show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTsForCollection'] } }, { label: 'Metadata', name: 'withMetadata', type: 'boolean', description: 'If set to true, returns NFT metadata; otherwise will only return tokenIds', default: true, optional: true, show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTsForCollection'] } }, { label: 'Limit', name: 'limit', type: 'number', description: 'Sets the total number of NFTs returned in the response. Defaults to 100.', default: 100, optional: true, show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTsForCollection'] } }, { label: 'Token Uri Timeout In Ms', name: 'tokenUriTimeoutInMs', type: 'number', description: 'No set timeout by default - When metadata is requested, this parameter is the timeout (in milliseconds) for the website hosting the metadata to respond. If you want to only access the cache and not live fetch any metadata for cache misses then set this value to 0.', optional: true, show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTsForCollection'] } } ] as INodeParams[] export const getOwnersForCollectionProperties = [ { label: 'Contract Address', name: 'contractAddress', type: 'string', description: 'Contract address for the NFT collection (ERC721 and ERC1155 supported).', default: '', show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getOwnersForCollection'] } }, { label: 'With Token Balances', name: 'withTokenBalances', type: 'boolean', description: 'If set to true the query will include the token balances per token id for each owner. false by default.', default: false, optional: true, show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getOwnersForCollection'] } }, { label: 'Block', name: 'block', type: 'string', description: 'The point in time or block number (in hex or decimal) to fetch collection ownership information for.', default: '', optional: true, show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getOwnersForCollection'] } }, { label: 'PageKey', name: 'pageKey', type: 'string', description: 'used for collections with >50,000 owners. pageKey field can be passed back as request parameter to get the next page of results.', default: '', show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getOwnersForCollection'] }, optional: true } ] as INodeParams[] export const getOwnersForTokenProperties = [ { label: 'Contract Address', name: 'contractAddress', type: 'string', description: 'Contract address for the NFT collection (ERC721 and ERC1155 supported).', default: '', show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': [ 'getOwnersForToken', 'isSpamContract', 'reingestContract', 'getFloorPrice', 'computeRarity', 'summarizeNFTAttributes', 'reportSpamContract' ] } }, { label: 'Token Id', name: 'tokenId', type: 'string', description: 'The ID of the token. Can be in hex or decimal format.', show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getOwnersForToken', 'computeRarity'] } } ] as INodeParams[] export const searchContractMetadataProperties = [ { label: 'Query', name: 'query', type: 'string', description: 'The search string that you want to search for in contract metadata', default: '', show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['searchContractMetadata'] } } ] as INodeParams[] export const isHolderOfCollectionProperties = [ { label: 'Contract Address', name: 'contractAddress', type: 'string', description: 'Contract address for the NFT collection (ERC721 and ERC1155 supported).', default: '', show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['isHolderOfCollection'] } }, { label: 'Wallet', name: 'wallet', type: 'string', description: 'Wallet address to check for collection ownership.', show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['isHolderOfCollection'] } } ] as INodeParams[] export const getNFTSalesProperties = [ { label: 'Contract Address', name: 'contractAddress', type: 'string', description: 'The contract address of a NFT collection to filter sales by. Defaults to returning all NFT contracts.', default: '', show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTSales'] } }, { label: 'Token Id', name: 'tokenId', type: 'string', description: 'The token ID of an NFT within the collection specified by contractAddress to filter sales by. Defaults to returning all token IDs.', show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTSales'] } }, { label: 'Start Block', name: 'startBlock', type: 'string', description: 'The block number to start fetching NFT sales data from. Allowed values are decimal integers and "latest". Defaults to "latest".', optional: true, default: 'latest', show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTSales'] } }, { label: 'Start Log Index', name: 'startLogIndex', type: 'number', description: 'The log index within the startBlock to start fetching NFT sales data from. Defaults to 0.', optional: true, default: 0, show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTSales'] } }, { label: 'Start Bundle Index', name: 'startBundleIndex', type: 'number', description: 'The index of an NFT within a sale bundle to start fetching NFT sales data from. Defaults to 0.', optional: true, default: 0, show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTSales'] } }, { label: 'Ascending Order', name: 'ascendingOrder', type: 'boolean', description: 'Whether to return the results ascending from startBlock or descending from startBlock. Defaults to ascending (true).', optional: true, default: true, show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTSales'] } }, { label: 'Marketplace', name: 'marketplace', type: 'options', description: 'The name of the NFT marketplace to filter sales by. Currently only "seaport" is supported.', options: [ { label: 'Seaport', name: 'seaport' } ], optional: true, default: 'seaport', show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTSales'] } }, { label: 'Buyer Address', name: 'buyerAddress', type: 'string', description: 'The address of the NFT buyer to filter sales by. Defaults to returning sales involving any buyer.', optional: true, show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTSales'] } }, { label: 'Seller Address', name: 'sellerAddress', type: 'string', description: 'The address of the NFT seller to filter sales by. Defaults to returning sales involving any seller.', optional: true, show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTSales'] } }, { label: 'Buyer Is Maker', name: 'buyerIsMaker', type: 'boolean', description: 'Filter by whether if the buyer was the maker in the trade, i.e. if the sale involved a buyer offering a bid and the seller then accepting the bid. Defaults to returning both maker and taker orders.', optional: true, show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTSales'] } }, { label: 'Limit', name: 'limit', type: 'number', description: 'The maximum number of NFT sales to return. Defaults to 100.', optional: true, show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getNFTSales'] } } ] as INodeParams[] export const getContractsForOwnerProperties = [ { label: 'Owner', name: 'owner', type: 'string', description: 'Address for NFT owner (can be in ENS format!).', placeholder: 'vitalk.eth', show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getContractsForOwner'] } }, { label: 'PageKey', name: 'pageKey', type: 'string', description: 'key for pagination. If more results are available, a pageKey will be returned in the response. Pass back the pageKey as a param to fetch the next page of results.', optional: true, show: { 'inputParameters.api': ['nftAPI'], 'inputParameters.operation': ['getContractsForOwner'] } } ] as INodeParams[] export const transactionReceiptsOperations = [ { name: 'alchemy_getTransactionReceipts', value: 'alchemy_getTransactionReceipts', parentGroup: 'Transaction Receipt Information', description: 'Fetch all transaction receipts for a block number or a block hash in one API call ', providerNetworks: { alchemy: [NETWORK.MAINNET, NETWORK.GÖRLI, NETWORK.MATIC, NETWORK.MATIC_MUMBAI, NETWORK.ARBITRUM, NETWORK.ARBITRUM_GOERLI] }, method: 'POST', body: { jsonrpc: '2.0', method: 'alchemy_getTransactionReceipts', params: [], id: 1 }, inputParameters: `
  • blockNumber - (hex) The block number you want to get transaction receipts for
  • blockHash - The block hash you want to get transaction receipts for
`, exampleParameters: `[ { "blockHash": "0xeb7214680220e50f1e9662d2f15569395f8e67455cc2f5cbf7db8b3b1567558c" } ]`, exampleResponse: { jsonrpc: '2.0', id: 1, result: { receipts: [ { transactionHash: '0x103c47bd1917f5b57c89d55bc9a664eca732fadcc7596670030ad27ac26259ae', blockHash: '0xbd6523808cf0a98c528b7e169b357c46a7cd0f602cec98f05bb5962553522647', blockNumber: '0xd63adc', contractAddress: null, cumulativeGasUsed: '0x26d93', effectiveGasPrice: '0x1e836343a7', from: '0xcc72f778eedd8e337e6cb58ca9ec8ba2912e71dc', gasUsed: '0x26d93', logs: [ { address: '0xa5def515cfd373d17830e7c1de1639cb3530a112', topics: [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', '0x000000000000000000000000cc72f778eedd8e337e6cb58ca9ec8ba2912e71dc', '0x0000000000000000000000001ca1a5937d73f74f89764c3835d6796e4e1c8314' ], data: '0x000000000000000000000000000000000000000000000df7b2d4343e99d38475', blockNumber: '0xd63adc', transactionHash: '0x103c47bd1917f5b57c89d55bc9a664eca732fadcc7596670030ad27ac26259ae', transactionIndex: '0x0', blockHash: '0xbd6523808cf0a98c528b7e169b357c46a7cd0f602cec98f05bb5962553522647', logIndex: '0x0', removed: false } ], logsBloom: '0x00000000000000000000000000000000000000001400000020000000000000000000000000000000000000000000000008000000000000000000000008240000000000000000000000000408000000000000000000040000000000002000000000000000000000000000010000000004000000000000000000000810000000000000000000000000000000000000000000000000000020000000000000000000020000000000800000000004000000020000000000000000000000000000000000008002000000000000000000000000020000000000000000000000000040000010000000000000000000020000000000000000400000000000000000000000', status: '0x1', to: '0x1ca1a5937d73f74f89764c3835d6796e4e1c8314', transactionIndex: '0x0', type: '0x2' } ] } } } ] as IETHOperation[] export const tokenAPIOperations = [ { name: 'alchemy_getTokenAllowance', value: 'alchemy_getTokenAllowance', parentGroup: 'Token Information', description: 'Returns the amount which the spender is allowed to withdraw from the owner.', providerNetworks: { alchemy: [ NETWORK.MAINNET, NETWORK.GÖRLI, NETWORK.MATIC, NETWORK.MATIC_MUMBAI, NETWORK.ARBITRUM, NETWORK.ARBITRUM_GOERLI, NETWORK.OPTIMISM, NETWORK.OPTIMISM_GOERLI ] }, method: 'POST', body: { jsonrpc: '2.0', method: 'alchemy_getTokenAllowance', params: [], id: 83 }, inputParameters: `
  • Object - An object with the following fields:
    • contract: DATA, 20 Bytes - The address of the token contract.
    • owner: DATA, 20 Bytes - The address of the token owner.
    • spender: DATA, 20 Bytes - The address of the token spender.
`, exampleParameters: `[ { "contract": "0xE41d2489571d322189246DaFA5ebDe1F4699F498", "owner": "0xe8095A54C83b069316521835408736269bfb389C", "spender": "0x3Bcc5bD4abBc853395eBE5103b7DbA20411E38db" } ]`, exampleResponse: { jsonrpc: '2.0', id: 83, result: '10963536149943846000' } }, { name: 'alchemy_getTokenBalances', value: 'alchemy_getTokenBalances', parentGroup: 'Token Information', description: 'Returns token balances for a specific address given a list of contracts.', providerNetworks: { alchemy: [ NETWORK.MAINNET, NETWORK.GÖRLI, NETWORK.MATIC, NETWORK.MATIC_MUMBAI, NETWORK.ARBITRUM, NETWORK.ARBITRUM_GOERLI, NETWORK.OPTIMISM, NETWORK.OPTIMISM_GOERLI ] }, method: 'POST', body: { jsonrpc: '2.0', method: 'alchemy_getTokenBalances', params: [], id: 42 }, inputParameters: `
  • DATA, 20 Bytes - The address for which token balances will be checked
  • One of:
    • Array - A list of contract addresses
    • String "DEFAULT_TOKENS" - denotes a query for the top 100 tokens by 24 hour volume
`, exampleParameters: `[ "0x3f5ce5fbfe3e9af3971dd833d26ba9b5c936f0be", [ "0x607f4c5bb672230e8672085532f7e901544a7375", "0x618e75ac90b12c6049ba3b27f5d5f8651b0037f6", "0x63b992e6246d88f07fc35a056d2c365e6d441a3d", "0x6467882316dc6e206feef05fba6deaa69277f155", "0x647f274b3a7248d6cf51b35f08e7e7fd6edfb271" ] ]`, exampleResponse: { jsonrpc: '2.0', id: 42, result: { address: '0x3f5ce5fbfe3e9af3971dd833d26ba9b5c936f0be', tokenBalances: [ { contractAddress: '0x607f4c5bb672230e8672085532f7e901544a7375', tokenBalance: '0x00000000000000000000000000000000000000000000000000003c005f81ab00', error: null } ] } } }, { name: 'alchemy_getTokenMetadata', value: 'alchemy_getTokenMetadata', parentGroup: 'Token Information', description: 'Returns metadata (name, symbol, decimals, logo) for a given token contract address.', providerNetworks: { alchemy: [ NETWORK.MAINNET, NETWORK.GÖRLI, NETWORK.MATIC, NETWORK.MATIC_MUMBAI, NETWORK.ARBITRUM, NETWORK.ARBITRUM_GOERLI, NETWORK.OPTIMISM, NETWORK.OPTIMISM_GOERLI ] }, method: 'POST', body: { jsonrpc: '2.0', method: 'alchemy_getTokenMetadata', params: [], id: 1 }, inputParameters: `
  • DATA, 20 Bytes - The address of the token contract.
`, exampleParameters: `[ "0x1985365e9f78359a9B6AD760e32412f4a445E862" ]`, exampleResponse: { jsonrpc: '2.0', id: 1, result: { logo: 'https://static.alchemyapi.io/images/assets/1104.png', symbol: 'REP', decimals: 18, name: 'Augur' } } } ] as IETHOperation[] ================================================ FILE: packages/components/nodes/Alchemy/solanaOperation.ts ================================================ import { NETWORK, NETWORK_PROVIDER } from '../../src/ChainNetwork' import { IETHOperation } from '../../src/ETHOperations' export const solanaOperationsNetworks = [NETWORK.SOLANA, NETWORK.SOLANA_DEVNET] export const solanaAPIOperations = [ { name: 'getTransaction', value: 'getTransaction', parentGroup: 'Reading & Writing Transactions', description: 'Returns transaction details for a confirmed transaction.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getTransaction', params: [], id: 1 }, inputParameters: `
  • base-58 encoded string - transaction signature
  • object - (optional) Config object:
    • commitment - (optional) Configures the commitment level of the blocks queried. Accepts one of the following strings:
      • finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • processed - the node will query its most recent block. Note that the block may not be complete.
    • encoding: string - (optional) encoding for tx data; Either "json", "jsonParsed", "base58" (slow), "base64". (default is "json")
    • maxSupportedTransactionVersion: number - (optional) set the max transaction version to return in responses. If the requested transaction is a higher version, an error will be returned.
`, exampleParameters: `[ "FhGuWorGjyu1sAMvn53GWb96apbExf8HvX18MVwexMQxmo2sweuSfFpoApJbMT19ijDHRRUk6kDbvE1kgNfRkse", { "commitment": "confirmed" } ]`, exampleResponse: { jsonrpc: '2.0', result: { blockTime: 1655319302, meta: { err: null, fee: 5000, innerInstructions: [ { index: 1, instructions: [ { accounts: [26, 13, 19, 15, 16, 0], data: '63S77LdPnZrhcJ2wGjQ7xuV', programIdIndex: 21 }, { accounts: [14, 16, 0], data: '3QCBRJNuTemd', programIdIndex: 26 }, { accounts: [2, 12, 19], data: '3KiXXdFFB5Km', programIdIndex: 26 } ] } ] } }, id: 1 } }, { name: 'sendTransaction', value: 'sendTransaction', parentGroup: 'Reading & Writing Transactions', description: 'Submits a signed transaction to the cluster for processing.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getTransaction', params: [], id: 1 }, inputParameters: `
  • encoded string - fully-signed Transaction
  • object - (optional) Config object:
    • skipPreflight: bool - if true, skip the preflight transaction checks (default: false)
    • preflightCommitment: string - (optional) Commitment level to use for preflight (default: "finalized").
      • finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • processed - the node will query its most recent block. Note that the block may not be complete.
    • encoding: string - (optional) Encoding used for the transaction data. Either "base58" (slow, DEPRECATED), or "base64". (default: "base58").
    • maxRetries: usize - (optional) Maximum number of times for the RPC node to retry sending the transaction to the leader. If this parameter is not provided, the RPC node will retry the transaction until it is finalized or until the blockhash expires.
    • minContextSlot: number - (optional) set the minimum slot that the request can be evaluated at.
`, exampleParameters: `[ "AeF71jBTbqF33iO17K/WepKejY6ED8Db2t00aizbkvRpDUaX6mKZMInTbDgrOLTTPgOmYbE96Nlt2IwGmZKSpQ0BAAsZeGK9wdFlyWz2mdFDfZ3/65ZjurcmrptLNm5mN3f8p0ogsmSV/IVZRQW5a5ssHj+xTkSMSg6MvA28piZAJSLyxSNlSqXrkQU25XOnSMYzHuJNOeei2iMWq3lwa8TdxeqjJRFwcKkY21dentnVTDwl1cueQS/QUd9mUuo25ivPwYY3RX3yAyUj3Q6IaALYsHU/017gPZpDvHJJ7jrgSdN8dzeHLDAOEIF1ci79StKCB8hABC87tDUy+cyHrGjv7ceJbKfgtajeraDLc3Ndv8FX/Azg8HJ4eCu29Y+gxBJROld53yhEmAooyHHkcMEPF/qybgtUUE4DMxUftcYRcEeGtXsupOBa7USG+TC3fPVLShGfKH//XF/H9EyQut9RHKTrnofS6+89GaTLV8VZXwcr3giCPPZtOymEVDquZoAthk6pIbU9bjTY+O+NVm9AJVKtkUh02Is8rj1xlTRevQ65RutmBvIsMFSykgwwkEcthzgnj9RgOcNPV63bOnMwtnA25xGVrA5WPR3Cp/qLwsfk76yDUU1ksxe/AT+BbeZD6tzuifWhovJqdEpfnl+qwU5RSmm+bf9lGWui27urEkLY0SuJudyP3580cJpbEGtHLw85u2ypzgSw/X8ulxaI4uU7M7MexO/4+iia6oyVTAFjLi12SQjOVE1oZb3vERv/YSsBOP45MdZXb1/RCItG0H924+Qvzh1Vy52i1Sl3ULQWHFoPCOzzP1aH2CdIP9hLVSu8Mn+Lr2Nkvn22qA5HyDclu6nN/XmuJp8Db7aXm0uYp3KSexdu9rcZZXaHIfHx8uTmLfbItKhf4aZ9tE3BLeXbMw96xmty3GWK/t8PSkFbQ/9hSRqTERLd8b2BR80bZBN1959YJRJtZlSAh0Y0/QrOBfhDaTInzNg0xDUbatfDR7qLZN0mJVepe+fnfUZ28WcGm4uYWqtTKkUJDehVf83cvmy378c6CmWwb5IDXbc+7Aan1RcYx3TJKFZjmGkdXraLXrijm0ttXHNVWyEAAAAABt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKnVgl9CjQpAihYbPBN/CahLRcSHNFl1MS9n9YRISJ2GJAYWBAwTERcBAxYEAhQVFwEDFgQKFBUXAQMWBAYOEBcBAxYGCRcMAgoGAQcWDwQNAwYBDAsIBQcJDxIAGAkR+PwcVAIAAAA=", { "encoding": "base64", "skipPreflight": true, "preflightCommitment": "processed" } ]`, exampleResponse: { jsonrpc: '2.0', result: '5WUPdF1zGiCbMX4dAGRrVJBvZuRjQatzsDJf8rcmLH8q67m8AoupcFsVNSo1CsPhLat4B3C2yZAtGp34yVgmcKNk' } }, { name: 'getSignatureStatuses', value: 'getSignatureStatuses', parentGroup: 'Reading & Writing Transactions', description: 'Returns the statuses of a list of signatures.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getSignatureStatuses', params: [], id: 1 }, inputParameters: `
  • array of base-58 encoded string - An array of transaction signatures to confirm
  • object - (optional) Config object:
    • searchTransactionHistory: bool - if true, a Solana node will search its ledger cache for any signatures not found in the recent status cache
`, exampleParameters: `[ [ "28P1gdVq52uEbCHns4EL5DCMjU5PtcBo5M3Gju4FX8DLwjLPDchudttnQapAxYy5dkdVZ6sqa6pvtgC5mbKLqfQA" ], { "searchTransactionHistory": true } ]`, exampleResponse: { jsonrpc: '2.0', result: { context: { slot: 137569378 }, value: [ { confirmationStatus: 'finalized', confirmations: null, err: null, slot: 137529522, status: { Ok: null } } ] } } }, { name: 'getSignaturesForAddress', value: 'getSignaturesForAddress', parentGroup: 'Reading & Writing Transactions', description: 'Returns signatures for confirmed transactions that include the given address in their accountKeys list. Returns signatures backwards in time from the provided signature or most recent confirmed block.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getSignaturesForAddress', params: [], id: 1 }, inputParameters: `
  • base-58 encoded string - account address
  • object - (optional) Config object:
    • limit: number - (optional) maximum transaction signatures to return (between 1 and 1,000, default: 1,000).
    • before: string - (optional) start searching backwards from this transaction signature. If not provided the search starts from the top of the highest max confirmed block.
    • until: string - (optional) search until this transaction signature, if found before limit reached.
    • minContextSlot: number - (optional) set the minimum slot that the request can be evaluated at.
    • commitment - (optional) Configures the commitment level of the blocks queried. Accepts one of the following strings:
      • finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • processed - the node will query its most recent block. Note that the block may not be complete.
`, exampleParameters: `[ "Vote111111111111111111111111111111111111111", { "limit": 1 } ]`, exampleResponse: { jsonrpc: '2.0', result: [ { blockTime: 1654173549, confirmationStatus: 'finalized', err: null, memo: null, signature: '67iWWgeXYSXxKmxMjAahr9ATXvv1SJoHedXYZxicFQtF4eFxCWJxUwEYczNbrua8pQAshmkf73gfAX5itutWTA7m', slot: 136105283 } ], id: 1 } }, { name: 'simulateTransaction', value: 'simulateTransaction', parentGroup: 'Reading & Writing Transactions', description: 'Simulate sending a transaction.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'simulateTransaction', params: [], id: 1 }, inputParameters: `
  • string - Transaction, as an encoded string. The transaction must have a valid blockhash, but is not required to be signed.
  • object - (optional) Configuration object containing the following optional fields:
    • sigVerify - if true the transaction signatures will be verified (default: false, conflicts with replaceRecentBlockhash).
    • commitment: string - (optional) Configures the commitment level of the blocks queried. Accepts one of the following strings:
      • finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • processed - the node will query its most recent block. Note that the block may not be complete.
    • encoding - (optional) Encoding used for the transaction data. Accepts one of the following strings: ["base64" (default), "base64+zstd" or "jsonParsed"]
    • replaceRecentBlockhash - (optional) if true the transaction recent blockhash will be replaced with the most recent blockhash. (default: false, conflicts with sigVerify).
    • accounts : object - (optional) Accounts configuration object containing the following fields:
      • encoding - (optional) encoding for returned Account data, either "base64" (default), "base64+zstd" or "jsonParsed". "jsonParsed" encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If "jsonParsed" is requested but a parser cannot be found, the field falls back to binary encoding, detectable when the data field is type string.
      • addresses - An array of accounts to return, as base-58 encoded strings.
    • minContextSlot: number - (optional) set the minimum slot that the request can be evaluated at.
`, exampleParameters: `[ "AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCAgiFt0xk+FyHf76k7/dkz9vjFqSY8vhrizSEx2GWidkQ5ewAKY7OUUt6ZEIryxOHEx1w1mGioe0SGujvGtbbPvaSDCG6EGTb0+Q1B98oAxfD0GiOZwOLQW1IkeOC71yX0NQm+LOK6+h53IwvrgMD9JNZme6u7PeARqlqZzOD4iRLRnyGE+3cPqKW5IiU66yIZZOgOtO4B9TYReBdaWsp+Dx5lyxj0FXRm/6oiIDpU9abZ6qIYeIKRR96fuhXeHpmjwUMJxNk9vhCZs3zhxL/0CZLdm0EbWEwrD4A7KBrPOYP2gbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCph9Yfdu8NCN6d9KtOQeMPovh7jKgKo56yxXQO8eTA6XUBBgcAAgEDBQQH4gMobmZ0X2FjY291bnQ6ICI5enlMTjVDUXd4V3JFUGl0MXNGeTVEWjV4TXFKZk5RVExpcUJWU2Z2UHZXWSIsCiAgICBkYXRhX2FjY291bnQ6ICIzZDhTcG5abnRtR0t0dlVtaHVhazdLU3U2WFQ3QVVNaW51VURtYTVobWlCWCIsCiAgICBjb2luX3NyY19hY2N0OiAiQkI3cGN0UGVWQ1FQalhNR2RrRFg5QURUOTk1Z1hDVkx5SEpVeWdwSlBOMngiLAogICAgY29pbl9kZXN0X2FjY3Q6ICI5UDY4a2J2VzlLelBIc1pURnE0eVFyUlZrWXNmNUNRRmc5aFZoeUJaZHBQMiIsCiAgICB0cmFuc2Zlcl9hdXRob3JpdHk6ICJwTWludFRaNUNFa0hVaGdydXJQZ29pTW50b0xnRndic0tkYnFOclhzcmR5IiwKICAgIGFjdGl2aXR5OiAxLCAKICAgIHBheV9wZXJpb2Q6IDYwNDgwMC4wMDAwMDAsIAogICAgcGF5X3JhdGU6IDguMDAwMDAwLAogICAgdGltZXN0YW1wOiAxNjU1MTg0ODYwLAogICAgbXVsdGlwbGllcjogMS4wLAogICAgbWF4X3BheW91dDogMTYuMDAwMDAwKQ==", { "encoding": "base64", "commitment": "recent", "sigVerify": false, "accounts": { "addresses": [ "9zyLN5CQwxWrEPit1sFy5DZ5xMqJfNQTLiqBVSfvPvWY", "GtFMtrW31RdCeSdW4ot3jNVuoLtFywJGGTiF1Q8Uopky", "pMintTZ5CEkHUhgrurPgoiMntoLgFwbsKdbqNrXsrdy", "3d8SpnZntmGKtvUmhuak7KSu6XT7AUMinuUDma5hmiBX", "9P68kbvW9KzPHsZTFq4yQrRVkYsf5CQFg9hVhyBZdpP2", "BB7pctPeVCQPjXMGdkDX9ADT995gXCVLyHJUygpJPN2x", "pSTAkE7Z2guBmSheg6tHdHk3rT1wmF1Mfuf52tnWeAd", "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" ] } } ]`, exampleResponse: { jsonrpc: '2.0', result: { context: { slot: 137569919 }, value: { accounts: [null, null, null, null, null, null, null, null], err: 'BlockhashNotFound', logs: [], unitsConsumed: 0 } }, id: 1 } }, { name: 'getBlockProduction', value: 'getBlockProduction', parentGroup: 'Getting Blocks', description: 'Returns recent block production information from the current or previous epoch.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getBlockProduction', params: [], id: 1 } }, { name: 'getBlock', value: 'getBlock', parentGroup: 'Getting Blocks', description: 'Returns identity and transaction information about a confirmed block in the ledger.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getBlock', params: [], id: 1 }, inputParameters: `
  • u64 - a slot integer denoting the target block number.
  • object - (optional) Configuration object containing the following optional fields:
    • transactionDetails: string - (optional) level of transaction detail to return. Accepts one of the following strings: ["full" (Default), "signatures", or "none"]
    • commitment: string - (optional) Configures the commitment level of the blocks queried. Accepts one of the following strings:
      • finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • processed - the node will query its most recent block. Note that the block may not be complete.
    • encoding: string - (optional) data encoding for each returned transaction. Accepts one of the following strings: ["json" (Default), "jsonParsed", "base58" (slow), "base64"]
    • rewards: bool - (optional) whether to populate the rewards array. true m(Default)
    • maxSupportedTransactionVersion: number - (optional) sets the maximum transaction version.
`, exampleParameters: `[ 430, { "encoding": "json", "transactionDetails":"full", "rewards":false } ]`, exampleResponse: { jsonrpc: '2.0', result: { blockHeight: 428, blockTime: null, blockhash: '3Eq21vXNB5s86c62bVuUfTeaMif1N2kUqRPBmGRJhyTA', parentSlot: 429, previousBlockhash: 'mfcyqEXB3DnHXki6KjjmZck6YjmZLvpAByy2fj4nh6B', transactions: [ { meta: { err: null, fee: 5000, innerInstructions: [], logMessages: [], postBalances: [499998932500, 26858640, 1, 1, 1], postTokenBalances: [], preBalances: [499998937500, 26858640, 1, 1, 1], preTokenBalances: [], status: { Ok: null } }, transaction: { message: { accountKeys: [ '3UVYmECPPMZSCqWKfENfuoTv51fTDTWicX9xmBD2euKe', 'AjozzgE83A3x1sHNUR64hfH7zaEBWeMaFuAN9kQgujrc', 'SysvarS1otHashes111111111111111111111111111', 'SysvarC1ock11111111111111111111111111111111', 'Vote111111111111111111111111111111111111111' ], header: { numReadonlySignedAccounts: 0, numReadonlyUnsignedAccounts: 3, numRequiredSignatures: 1 }, instructions: [ { accounts: [1, 2, 3, 0], data: '37u9WtQpcm6ULa3WRQHmj49EPs4if7o9f1jSRVZpm2dvihR9C8jY4NqEwXUbLwx15HBSNcP1', programIdIndex: 4 } ], recentBlockhash: 'mfcyqEXB3DnHXki6KjjmZck6YjmZLvpAByy2fj4nh6B' }, signatures: ['2nBhEBYYvfaAe16UMNqRHre4YNSskvuYgx3M6E4JP1oDYvZEJHvoPzyUidNgNX5r9sTyN1J9UxtbCXy2rqYcuyuv'] } } ] }, id: 1 } }, { name: 'getBlocks', value: 'getBlocks', parentGroup: 'Getting Blocks', description: 'Returns a list of confirmed blocks between two slots.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getBlocks', params: [], id: 1 }, inputParameters: `
  • u64 - starting slot integer
  • u64 - (optional) ending slot integer
  • commitment: string - (optional) Configures the commitment level of the blocks queried. Accepts one of the following strings:
    • finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
    • confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
    • processed - the node will query its most recent block. Note that the block may not be complete.
`, exampleParameters: `[5, 10]`, exampleResponse: { jsonrpc: '2.0', result: [5, 6, 7, 8, 9, 10], id: 1 } }, { name: 'getBlocksWithLimit', value: 'getBlocksWithLimit', parentGroup: 'Getting Blocks', description: 'Returns a list of confirmed blocks starting at the given slot.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getBlocksWithLimit', params: [], id: 1 }, inputParameters: `
  • u64 - starting slot integer
  • u64 - (optional) ending slot integer
  • commitment: string - (optional) Configures the commitment level of the blocks queried. Accepts one of the following strings:
    • finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
    • confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
    • processed - the node will query its most recent block. Note that the block may not be complete.
`, exampleParameters: `[5, 3]`, exampleResponse: { jsonrpc: '2.0', result: [5, 6, 7], id: 1 } }, { name: 'getBlockHeight', value: 'getBlockHeight', parentGroup: 'Getting Blocks', description: 'Returns the current block height of the node.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getBlockHeight', params: [], id: 1 } }, { name: 'isBlockhashValid', value: 'isBlockhashValid', parentGroup: 'Getting Blocks', description: 'Returns whether a blockhash is still valid or not.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'isBlockhashValid', params: [], id: 1 }, inputParameters: `
  • blockhash: base-58 encoded string - the blockhash of this block
  • object - (optional) Configuration object containing the following optional fields:
    • commitment: string - (optional) Configures the commitment level of the blocks queried. Accepts one of the following strings:
      • finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • processed - the node will query its most recent block. Note that the block may not be complete.
    • minContextSlot: number - (optional) set the minimum slot that the request can be evaluated at
`, exampleParameters: `[ "J7rBdM6AecPDEZp8aPq5iPSNKVkU5Q76F3oAV4eW5wsW", { "commitment": "processed" } ]`, exampleResponse: { jsonrpc: '2.0', result: { context: { slot: 136103237 }, value: false }, id: 1 } }, { name: 'getTokenAccountsByOwner', value: 'getTokenAccountsByOwner', parentGroup: 'Token Information', description: 'Returns all SPL Token accounts by token owner.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getTokenAccountsByOwner', params: [], id: 1 }, inputParameters: `
  • base-58 encoded string - Pubkey of queried SPL token account owner
  • object - Either:
    • mint - Pubkey of the specific token Mint to limit accounts to, as base-58 encoded string.
    • programId - Pubkey of the Token program ID that owns the accounts, as base-58 encoded string.
  • object - (optional) Configuration object containing the following optional fields:
    • commitment: string - (optional) Configures the commitment level of the blocks queried. Accepts one of the following strings:
      • finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • processed - the node will query its most recent block. Note that the block may not be complete.
    • encoding: string - (optional) data encoding for each returned transaction. Accepts one of the following strings: ["json" (Default), "jsonParsed", "base58" (slow), "base64"]
    • minContextSlot: number - (optional) set the minimum slot that the request can be evaluated at
    • dataSlice: object - (optional) limits the returned account data using the provided offset: usize and length: usize fields; only available for "base58", "base64" or "base64+zstd" encodings.
`, exampleParameters: `[ "J27ma1MPBRvmPJxLqBqQGNECMXDm9L6abFa4duKiPosa", { "mint": "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk" }, { "encoding": "jsonParsed" } ]`, exampleResponse: { jsonrpc: '2.0', result: { context: { slot: 137568828 }, value: [ { account: { data: { parsed: { info: { isNative: false, mint: '2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk', owner: 'J27ma1MPBRvmPJxLqBqQGNECMXDm9L6abFa4duKiPosa', state: 'initialized', tokenAmount: { amount: '821', decimals: 6, uiAmount: 8.21e-4, uiAmountString: '0.000821' } }, type: 'account' }, program: 'spl-token', space: 165 }, executable: false, lamports: 2039280, owner: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', rentEpoch: 318 }, pubkey: 'Exo9AH6fNchE43GaJB85FT7ToYiuKnKzYDyW5mFeTXRR' } ] }, id: 1 } }, { name: 'getTokenAccountBalance', value: 'getTokenAccountBalance', parentGroup: 'Token Information', description: 'Returns the token balance of an SPL Token account.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getTokenAccountBalance', params: [], id: 1 }, inputParameters: `
  • base-58 encoded string - Pubkey of queried token account
  • object - (optional) Configuration object containing the following optional fields:
    • commitment: string - (optional) Configures the commitment level of the blocks queried. Accepts one of the following strings:
      • finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • processed - the node will query its most recent block. Note that the block may not be complete.
`, exampleParameters: `[ "3Lz6rCrXdLybFiuJGJnEjv6Z2XtCh5n4proPGP2aBkA1" ]`, exampleResponse: { jsonrpc: '2.0', result: { context: { slot: 137567036 }, value: { amount: '301922375078', decimals: 6, uiAmount: 301922.375078, uiAmountString: '301922.375078' } }, id: 1 } }, { name: 'getTokenSupply', value: 'getTokenSupply', parentGroup: 'Token Information', description: 'Returns the total supply of an SPL Token type.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getTokenSupply', params: [], id: 1 }, inputParameters: `
  • base-58 encoded string - Pubkey of token Mint to query
  • object - (optional) Configuration object containing the following optional fields:
    • commitment: string - (optional) Configures the commitment level of the blocks queried. Accepts one of the following strings:
      • finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • processed - the node will query its most recent block. Note that the block may not be complete.
`, exampleParameters: `[ "HfYFjMKNZygfMC8LsQ8LtpPsPxEJoXJx4M6tqi75Hajo" ]`, exampleResponse: { jsonrpc: '2.0', result: { context: { slot: 137571639 }, value: { amount: '999999999997060679', decimals: 9, uiAmount: 9.999999999970608e8, uiAmountString: '999999999.997060679' } }, id: 1 } }, { name: 'getEpochSchedule', value: 'getEpochSchedule', parentGroup: 'Network Information', description: `Returns epoch schedule information from this cluster's genesis config.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getEpochSchedule', params: [], id: 1 } }, { name: 'getEpochInfo', value: 'getEpochInfo', parentGroup: 'Network Information', description: `Returns information about the current epoch.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getEpochInfo', params: [], id: 1 } }, { name: 'getFeeForMessage', value: 'getFeeForMessage', parentGroup: 'Network Information', description: `Get the fee the network will charge for a particular message.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getFeeForMessage', params: [], id: 1 }, inputParameters: `
  • base-58 encoded string - encoded Message
  • object - (optional) Configuration object containing the following optional fields:
    • commitment: string - (optional) Configures the commitment level of the blocks queried. Accepts one of the following strings:
      • finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • processed - the node will query its most recent block. Note that the block may not be complete.
`, exampleParameters: `["AQABAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAA"]`, exampleResponse: { jsonrpc: '2.0', result: { context: { slot: 135143215 }, value: null }, id: 1 } }, { name: 'getHighestSnapshotSlot', value: 'getHighestSnapshotSlot', parentGroup: 'Network Information', description: `Returns the highest slot information that the node has snapshots for.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getHighestSnapshotSlot', params: [], id: 1 } }, { name: 'getGenesisHash', value: 'getGenesisHash', parentGroup: 'Network Information', description: `Returns the genesis hash.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getGenesisHash', params: [], id: 1 } }, { name: 'getRecentPerformanceSamples', value: 'getRecentPerformanceSamples', parentGroup: 'Network Information', description: `Returns a list of recent performance samples, in reverse slot order. Performance samples are taken every 60 seconds and include the number of transactions and slots that occur in a given time window.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getRecentPerformanceSamples', params: [], id: 1 } }, { name: 'getFirstAvailableBlock', value: 'getFirstAvailableBlock', parentGroup: 'Network Information', description: `Get the fee the network will charge for a particular message.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getFirstAvailableBlock', params: [], id: 1 } }, { name: 'getMinimumBalanceForRentExemption', value: 'getMinimumBalanceForRentExemption', parentGroup: 'Network Information', description: `Returns minimum balance required to make account rent exempt.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getMinimumBalanceForRentExemption', params: [], id: 1 }, inputParameters: `
  • usize - account data length
  • object - (optional) Configuration object containing the following optional fields:
    • commitment: string - (optional) Configures the commitment level of the blocks queried. Accepts one of the following strings:
      • finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • processed - the node will query its most recent block. Note that the block may not be complete.
`, exampleParameters: `[ 50 ]`, exampleResponse: { jsonrpc: '2.0', result: 1238880, id: 1 } }, { name: 'getClusterNodes', value: 'getClusterNodes', parentGroup: 'Node Information', description: `Returns information about all the nodes participating in the cluster.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getClusterNodes', params: [], id: 1 } }, { name: 'getHealth', value: 'getHealth', parentGroup: 'Node Information', description: `Returns the current health of the node.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getHealth', params: [], id: 1 } }, { name: 'getVersion', value: 'getVersion', parentGroup: 'Node Information', description: `Returns the current solana versions running on the node.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getVersion', params: [], id: 1 } }, { name: 'getIdentity', value: 'getIdentity', parentGroup: 'Node Information', description: `Returns the identity pubkey for the current node.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getIdentity', params: [], id: 1 } }, { name: 'getInflationGovernor', value: 'getInflationGovernor', parentGroup: 'Network Inflation', description: `Returns the current inflation governor.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getInflationGovernor', params: [], id: 1 } }, { name: 'getInflationReward', value: 'getInflationReward', parentGroup: 'Network Inflation', description: `Returns the inflation / staking reward for a list of addresses for an epoch.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getInflationReward', params: [], id: 1 }, inputParameters: `
  • array of base-58 encoded strings - An array of addresses to query
  • object - (optional) Configuration object containing the following optional fields:
    • commitment: string - (optional) Configures the commitment level of the blocks queried. Accepts one of the following strings:
      • finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • processed - the node will query its most recent block. Note that the block may not be complete.
    • epoch: u64 - (optional) An epoch for which the reward occurs. If omitted, the previous epoch will be used
    • minContextSlot: number - (optional) set the minimum slot that the request can be evaluated at
`, exampleParameters: `[ ["9zyLN5CQwxWrEPit1sFy5DZ5xMqJfNQTLiqBVSfvPvWY"], { "commitment": "confirmed" } ]`, exampleResponse: { jsonrpc: '2.0', result: 1238880, id: 1 } }, { name: 'getInflationRate', value: 'getInflationRate', parentGroup: 'Network Inflation', description: `Returns the specific inflation values for the current epoch.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getInflationRate', params: [], id: 1 } }, { name: 'getSupply', value: 'getSupply', parentGroup: 'Network Inflation', description: `Returns information about the current supply.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getSupply', params: [], id: 1 } }, { name: 'getBalance', value: 'getBalance', parentGroup: 'Account Information', description: `Returns the balance of the account of provided Pubkey.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getBalance', params: [], id: 1 }, inputParameters: `
  • base-58 encoded string - Pubkey of account to query
  • object - (optional) Configuration object containing the following optional fields:
    • commitment: string - (optional) Configures the commitment level of the blocks queried. Accepts one of the following strings:
      • finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • processed - the node will query its most recent block. Note that the block may not be complete.
    • minContextSlot: number - (optional) set the minimum slot that the request can be evaluated at
`, exampleParameters: `[ "83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri" ]`, exampleResponse: { jsonrpc: '2.0', result: { context: { slot: 1 }, value: 0 }, id: 1 } }, { name: 'getLargestAccounts', value: 'getLargestAccounts', parentGroup: 'Account Information', description: `Returns the 20 largest accounts, by lamport balance (results may be cached up to two hours).`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getLargestAccounts', params: [], id: 1 } }, { name: 'getAccountInfo', value: 'getAccountInfo', parentGroup: 'Account Information', description: `Returns all information associated with the account of provided Pubkey.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getAccountInfo', params: [], id: 1 }, inputParameters: `
  • base-58 encoded string - Pubkey of account to query
  • object - (optional) Configuration object containing the following optional fields:
    • encoding: string - (optional) data encoding for each returned transaction. Accepts one of the following strings: ["json" (Default), "jsonParsed", "base58" (slow), "base64"]
    • dataSlice: object - (optional) limits the returned account data using the provided offset: usize and length: usize fields; only available for "base58", "base64" or "base64+zstd" encodings.
`, exampleParameters: `[ "vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg", { "encoding": "base58" } ]`, exampleResponse: { jsonrpc: '2.0', result: { context: { slot: 134461197 }, value: { data: ['', 'base58'], executable: false, lamports: 410431055, owner: '11111111111111111111111111111111', rentEpoch: 311 } }, id: 1 } }, { name: 'getVoteAccounts', value: 'getVoteAccounts', parentGroup: 'Account Information', description: `Returns the account info and associated stake for all the voting accounts in the current bank.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getVoteAccounts', params: [], id: 1 } }, { name: 'getMultipleAccounts', value: 'getMultipleAccounts', parentGroup: 'Account Information', description: `Returns the account information for a list of Pubkeys.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getMultipleAccounts', params: [], id: 1 }, inputParameters: `
  • array of base-58 encoded strings - An array of addresses to query
  • object - (optional) Configuration object containing the following optional fields:
    • encoding: string - (optional) data encoding for each returned transaction. Accepts one of the following strings: ["json" (Default), "jsonParsed", "base58" (slow), "base64"]
    • dataSlice: object - (optional) limits the returned account data using the provided offset: usize and length: usize fields; only available for "base58", "base64" or "base64+zstd" encodings.
    • minContextSlot: number - (optional) set the minimum slot that the request can be evaluated at
`, exampleParameters: `[ [ "vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg", "4fYNw3dojWmQ4dXtSGE9epjRGy9pFSx62YypT7avPYvA" ], { "dataSlice": { "offset": 0, "length": 0 } } ]`, exampleResponse: { jsonrpc: '2.0', result: { context: { slot: 136100846 }, value: [ { data: ['', 'base64'], executable: false, lamports: 410426055, owner: '11111111111111111111111111111111', rentEpoch: 314 }, { data: ['', 'base64'], executable: false, lamports: 2000000, owner: '11111111111111111111111111111111', rentEpoch: 314 } ] }, id: 1 } }, { name: 'getProgramAccounts', value: 'getProgramAccounts', parentGroup: 'Account Information', description: `Returns all accounts owned by the provided program Pubkey.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getProgramAccounts', params: [], id: 1 }, inputParameters: `
  • base-58 encoded string - Pubkey of program
  • object - (optional) Configuration object containing the following optional fields:
    • encoding: string - (optional) data encoding for each returned transaction. Accepts one of the following strings: ["json" (Default), "jsonParsed", "base58" (slow), "base64"]
    • dataSlice: object - (optional) limits the returned account data using the provided offset: usize and length: usize fields; only available for "base58", "base64" or "base64+zstd" encodings.
    • minContextSlot: number - (optional) set the minimum slot that the request can be evaluated at
    • withContext: bool - (optional) wrap the result in an RpcResponse JSON object.
    • filters: array - (optional) filter results using various filter objects; account must meet all filter criteria to be included in results
      • memcmp: object - (optional) compares a provided series of bytes with program account data at a particular offset. Fields:
        • offset: usize - (optional) offset into program account data to start comparison
        • bytes: string - (optional) data to match, as base-58 encoded string and limited to less than 129 bytes
      • dataSize: u64 - (optional) compares the program account data length with the provided data size
`, exampleParameters: `[ "Stake11111111111111111111111111111111111111", { "encoding": "jsonParsed", "commitment": "recent", "filters": [ { "memcmp": { "offset": 44, "bytes": "65qFmhCmDgXjg1duFdcpYyPheWyWGyusZhy3Y8khMoYm" } } ] } ]`, exampleResponse: { jsonrpc: '2.0', result: [ { account: { data: '2R9jLfiAQ9bgdcw6h8s44439', executable: false, lamports: 15298080, owner: '4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T', rentEpoch: 28 }, pubkey: 'CxELquR1gPP8wHe33gZ4QxqGB3sZ9RSwsJ2KshVewkFY' } ], id: 1 } }, { name: 'minimumLedgerSlot', value: 'minimumLedgerSlot', parentGroup: 'Slot Information', description: `Returns the lowest slot that the node has information about in its ledger. This value may increase over time if the node is configured to purge older ledger data.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'minimumLedgerSlot', params: [], id: 1 } }, { name: 'getMaxShredInsertSlot', value: 'getMaxShredInsertSlot', parentGroup: 'Slot Information', description: `Get the max slot seen from after shred insert`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getMaxShredInsertSlot', params: [], id: 1 } }, { name: 'getSlotLeader', value: 'getSlotLeader', parentGroup: 'Slot Information', description: `Returns the current slot leader.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getSlotLeader', params: [], id: 1 } }, { name: 'getSlotLeaders', value: 'getSlotLeaders', parentGroup: 'Slot Information', description: `Returns the slot leaders for a given slot range.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getSlotLeader', params: [], id: 1 }, inputParameters: `
  • array - array with first item as start slot, second item as limit:
    • u64 - Start slot
    • u64 - Limit of the number of slot leaders in the response payload.
`, exampleParameters: `[ 5, 10 ]` }, { name: 'getSlot', value: 'getSlot', parentGroup: 'Slot Information', description: `Returns the slot that has reached the given or default commitment level.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getSlot', params: [], id: 1 } }, { name: 'getMaxRetransmitSlot', value: 'getMaxRetransmitSlot', parentGroup: 'Slot Information', description: `Get the max slot seen from retransmit stage.`, providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getMaxRetransmitSlot', params: [], id: 1 } } ] as IETHOperation[] ================================================ FILE: packages/components/nodes/Alchemy/subscribeOperation.ts ================================================ import { NETWORK, NETWORK_PROVIDER } from '../../src/ChainNetwork' import { alchemySupportedNetworks, IETHOperation } from '../../src/ETHOperations' export const subsOperationsNetworks = [...alchemySupportedNetworks] export const solanaOperationsNetworks = [NETWORK.SOLANA, NETWORK.SOLANA_DEVNET, NETWORK.SOLANA_TESTNET] export const subscribeOperations = [ { name: 'Eth Subscribe (eth_subscribe)', value: 'eth_subscribe', parentGroup: 'Subscribe', description: 'Starts a subscription to a specific event.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: subsOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'eth_subscribe', params: [] }, inputParameters: `
  • subscription name: string - The type of event you want to subscribe to (i.e., newHeads, logs, pendingTransactions, newPendingTransactions). This method supports the following subscription types:
    • alchemy_pendingTransactions - Returns full transactions that are sent to the network, marked as pending, and are sent from or to a certain address. (NOT supported on Arbitrum and Optimism)
    • newPendingTransactions - Returns the hash for all transactions that are added to the pending state and are signed with a key that is available in the node. (NOT supported on Arbitrum and Optimism)
    • newHeads - Fires a notification each time a new header is appended to the chain, including chain reorganizations.
    • logs - Returns logs that are included in new imported blocks and match the given filter criteria.
  • data: object - (Optional) - Arguments such as an address, multiple addresses, and topics. Note, only logs that are created from these addresses or match the specified topics will return logs.
`, exampleParameters: `["newHeads"]` }, { name: 'Log Subscribe (logSubscribe)', value: 'logSubscribe', parentGroup: 'Subscribe', description: '(Subscription Websocket) Subscribe to transaction logging.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'logSubscribe', params: [] }, inputParameters: `
  • filters: Object - filter criteria for the logs to receive results by account type; currently supported mentions:
    • mentions - subscribe to all transactions that mention the provided Pubkey (as base-58 encoded string), only accepts one account address
  • Object - (optional) Configuration object containing the following optional fields:
    • Commitment: ENUM - (optional) All fields are strings.
      • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • Processed - the node will query its most recent block. Note that the block may not be complete.
`, exampleParameters: `[ { "mentions": [ "11111111111111111111111111111111" ] }, { "commitment": "finalized" } ]` }, { name: 'Signature Subscribe (signatureSubscribe)', value: 'signatureSubscribe', parentGroup: 'Subscribe', description: '(Subscription Websocket) Subscribe to a transaction signature to receive notification when the transaction is confirmed On signatureNotification, the subscription is automatically cancelled.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'signatureSubscribe', params: [] }, inputParameters: `
  • String - Transaction Signature, as base-58 encoded string
  • Object - (optional) Configuration object containing the following optional fields:
    • Commitment: ENUM - (optional) All fields are strings.
      • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • Processed - the node will query its most recent block. Note that the block may not be complete.
`, exampleParameters: `[ "51y9Hf2cFzrUPDH24qvL6b6PtPMDGQSX3WwiHsSvkdfGiFTKdoJwGkvqS3gny6XNPLtUtRwGERAs45639EfR5XfT", { "commitment": "finalized" } ]` }, { name: 'Slot Subscribe (slotSubscribe)', value: 'slotSubscribe', parentGroup: 'Subscribe', description: '(Subscription Websocket) Subscribe to receive notification anytime a slot is processed by the validator.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'slotSubscribe', params: [] } } ] as IETHOperation[] export const unsubscribeOperations = [ { name: 'Eth Unsubscribe (eth_unsubscribe)', value: 'eth_unsubscribe', parentGroup: 'Unsubscribe', description: 'Cancels an existing subscription so that no further events are sent.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: subsOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'eth_unsubscribe', params: [] } }, { name: 'Log Unsubscribe (logUnsubscribe)', value: 'logUnsubscribe', parentGroup: 'Unsubscribe', description: '(Subscription Websocket) Unsubscribe from transaction logging.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'logUnsubscribe', params: [] } }, { name: 'Signature Unsubscribe (signatureUnsubscribe)', value: 'signatureUnsubscribe', parentGroup: 'Unsubscribe', description: '(Subscription Websocket) Unsubscribe from signature confirmation notification.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'signatureUnsubscribe', params: [] } }, { name: 'Slot Unsubscribe (slotUnsubscribe)', value: 'slotUnsubscribe', parentGroup: 'Unsubscribe', description: '(Subscription Websocket) Unsubscribe from slot notifications.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'slotUnsubscribe', params: [] } } ] as IETHOperation[] ================================================ FILE: packages/components/nodes/Alchemy/supportedNetwork.ts ================================================ import { INodeOptionsValue } from '../../src' import { NETWORK, NETWORK_LABEL } from '../../src/ChainNetwork' export const AlchemySupportedNetworks = [ { label: NETWORK_LABEL.MAINNET, name: NETWORK.MAINNET }, { label: NETWORK_LABEL.GÖRLI, name: NETWORK.GÖRLI }, { label: NETWORK_LABEL.MATIC, name: NETWORK.MATIC }, { label: NETWORK_LABEL.MATIC_MUMBAI, name: NETWORK.MATIC_MUMBAI }, { label: NETWORK_LABEL.ARBITRUM, name: NETWORK.ARBITRUM }, { label: NETWORK_LABEL.ARBITRUM_GOERLI, name: NETWORK.ARBITRUM_GOERLI }, { label: NETWORK_LABEL.OPTIMISM, name: NETWORK.OPTIMISM }, { label: NETWORK_LABEL.OPTIMISM_GOERLI, name: NETWORK.OPTIMISM_GOERLI }, { label: NETWORK_LABEL.SOLANA, name: NETWORK.SOLANA }, { label: NETWORK_LABEL.SOLANA_DEVNET, name: NETWORK.SOLANA_DEVNET } ] as INodeOptionsValue[] ================================================ FILE: packages/components/nodes/Arbiscan/Arbiscan.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' import { NETWORK, NETWORK_LABEL, etherscanAPIs } from '../../src/ChainNetwork' import axios, { AxiosRequestConfig, Method } from 'axios' import { SORT_BY, OPERATIONS, GET_ETHER_BALANCE, GET_MULTI_ETHER_BALANCE, GET_NORMAL_TRANSACTIONS, GET_INTERNAL_TRANSACTIONS, GET_INTERNAL_TRANSACTIONS_BY_HASH, GET_INTERNAL_TRANSACTIONS_BY_BLOCK, GET_ABI, GET_CONTRACT_SOURCE_CODE, CHECK_TRANSACTION_RECEIPT_STATUS, CHECK_CONTRACT_EXECUTION_STATUS, GET_ERC20_TOKEN_SUPPLY, GET_ERC20_TOKEN_BALANCE, GET_ETHER_PRICE, GET_ETHER_SUPPLY } from './constants' class Arbiscan implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions: INodeParams[] credentials?: INodeParams[] networks?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Arbiscan' this.name = 'arbiscan' this.icon = 'arbiscan.png' this.type = 'action' this.category = 'Block Explorer' this.version = 1.0 this.description = 'Perform Arbiscan operations' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'API', name: 'api', type: 'options', options: [ { label: 'Get Ether Balance for a Single Address', name: GET_ETHER_BALANCE.name, description: 'Returns the Ether balance of a given address.' }, { label: 'Get Ether Balance for Multiple Addresses(separated by a comma)', name: GET_MULTI_ETHER_BALANCE.name, description: 'Returns the Ether balance of the addresses(each address separated by a comma) entered.' }, { label: 'Get transactions', name: GET_NORMAL_TRANSACTIONS.name, description: 'Returns the list of transactions performed by an address, with optional pagination.' }, { label: 'Get internal transactions', name: GET_INTERNAL_TRANSACTIONS.name, description: 'Returns the list of internal transactions performed by an address, with optional pagination.' }, { label: 'Get internal transactions by hash', name: GET_INTERNAL_TRANSACTIONS_BY_HASH.name, description: 'Returns the list of internal transactions performed within a transaction.' }, { label: 'Get internal transactions by block', name: GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name, description: 'Returns the list of internal transactions performed within a block range, with optional pagination.' }, { label: 'Get Contract ABI', name: GET_ABI.name, description: 'Returns the contract Application Binary Interface ( ABI ) of a verified smart contract.' }, { label: 'Get Contract Source Code', name: GET_CONTRACT_SOURCE_CODE.name, description: 'Returns the Solidity source code of a verified smart contract.' }, { label: 'Check Transaction Receipt Status', name: CHECK_TRANSACTION_RECEIPT_STATUS.name, description: 'Returns the status code of a transaction execution.' }, { label: 'Check Contract Execution Status', name: CHECK_CONTRACT_EXECUTION_STATUS.name, description: 'Returns the status code of a contract execution.' }, { label: 'Get ERC20 Token Supply', name: GET_ERC20_TOKEN_SUPPLY.name, description: `Returns the total supply of a ERC-20 token. The result is returned in the token's smallest decimal representation. Eg. a token with a balance of 215.241526476136819398 and 18 decimal places will be returned as 215241526476136819398` }, { label: 'Get ERC20 Token Balance', name: GET_ERC20_TOKEN_BALANCE.name, description: `Returns the current balance of a ERC-20 token of an address. The result is returned in the token's smallest decimal representation. Eg. a token with a balance of 215.241526476136819398 and 18 decimal places will be returned as 215241526476136819398` }, { label: 'Get ETHER Price', name: GET_ETHER_PRICE.name, description: 'Returns the latest price of 1 ETHER.' }, { label: 'Get Total Supply of Ether', name: GET_ETHER_SUPPLY.name, description: 'Returns the current amount of Ether in circulation.' } ], default: 'getEtherBalance' } ] as INodeParams[] this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [ { label: NETWORK_LABEL.ARBITRUM, name: NETWORK.ARBITRUM }, { label: NETWORK_LABEL.ARBITRUM_GOERLI, name: NETWORK.ARBITRUM_GOERLI } ], default: 'homestead' } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Arbiscan API Key', name: 'arbiscanApi' } ], default: 'arbiscanApi' } ] as INodeParams[] this.inputParameters = [ { label: 'Address', name: 'address', type: 'string', description: 'The address parameter(s) required', show: { 'actions.api': [ GET_ETHER_BALANCE.name, GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_ABI.name, GET_CONTRACT_SOURCE_CODE.name, GET_ERC20_TOKEN_BALANCE.name ] } }, { label: 'Start Block', name: 'startBlock', type: 'number', optional: true, description: 'the block number to start searching for transactions', show: { 'actions.api': [GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name] }, default: 0 }, { label: 'End Block', name: 'endBlock', type: 'number', optional: true, description: 'the block number to stop searching for transactions', show: { 'actions.api': [GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name] } }, { label: 'Page', name: 'page', type: 'number', optional: true, description: 'the page number, if pagination is enabled', show: { 'actions.api': [GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name] }, default: 1 }, { label: 'Offset', name: 'offset', type: 'number', optional: true, description: 'the number of transactions displayed per page', show: { 'actions.api': [GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name] }, default: 10 }, { label: 'Sort By', name: 'sortBy', type: 'options', optional: true, options: SORT_BY, show: { 'actions.api': [GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name] }, default: 'desc' }, { label: 'Transaction Hash', name: 'txhash', type: 'string', description: 'the string representing the transaction hash to check for internal transactions', show: { 'actions.api': [ GET_INTERNAL_TRANSACTIONS_BY_HASH.name, CHECK_TRANSACTION_RECEIPT_STATUS.name, CHECK_CONTRACT_EXECUTION_STATUS.name ] } }, { label: 'Contract Address', name: 'contractAddress', type: 'string', description: 'the contract address of the ERC-20 token', show: { 'actions.api': [GET_ERC20_TOKEN_SUPPLY.name, GET_ERC20_TOKEN_BALANCE.name] } }, { label: 'Tag', name: 'tag', type: 'options', options: [{ label: 'latest', name: 'latest' }], default: 'latest', show: { 'actions.api': [GET_ERC20_TOKEN_BALANCE.name] } } ] as INodeParams[] } getNetwork(network: NETWORK): string { return `${etherscanAPIs[network]}` } getBaseParams(api: string) { const operation = OPERATIONS.filter(({ name }) => name === api)[0] return { module: operation.module, action: operation.action } } getISODate(date: Date) { return date.toISOString().split('T')[0] } async run(nodeData: INodeData): Promise { const actionData = nodeData.actions const networksData = nodeData.networks const inputParameters = nodeData.inputParameters const credentials = nodeData.credentials if (actionData === undefined || inputParameters === undefined || credentials === undefined || networksData === undefined) { throw new Error('Required data missing') } // GET api const api = actionData.api as string // GET network const network = networksData.network as NETWORK // GET credentials const apiKey = credentials.apiKey as string // GET address const address = inputParameters.address as string const startblock = inputParameters.startBlock as number const endblock = inputParameters.endBlock as number const page = inputParameters.page as number const offset = inputParameters.offset as number const sort = inputParameters.sortBy as string const txhash = inputParameters.txhash as string const blocktype = inputParameters.blockType as string const contractaddress = inputParameters.contractAddress as string const tag = inputParameters.tag as string const startTime = inputParameters.startTime as string const endTime = inputParameters.endTime as string const startdate = startTime ? this.getISODate(new Date(startTime)) : undefined const enddate = endTime ? this.getISODate(new Date(endTime)) : undefined const url = this.getNetwork(network) const { module, action } = this.getBaseParams(api) const queryParameters = { module, action, address, apiKey, startblock, endblock, page, offset, sort, txhash, blocktype, contractaddress, tag, startdate, enddate } const returnData: ICommonObject[] = [] let responseData: any try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url, params: queryParameters, paramsSerializer: (params) => serializeQueryParams(params), headers: { 'Content-Type': 'application/json' } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: Arbiscan } ================================================ FILE: packages/components/nodes/Arbiscan/constants.ts ================================================ // Account export const GET_ETHER_BALANCE = { name: 'getEtherBalance', module: 'account', action: 'balance' } export const GET_MULTI_ETHER_BALANCE = { name: 'getEtherBalanceMulti', module: 'account', action: 'balancemulti' } export const GET_NORMAL_TRANSACTIONS = { name: 'getTransactions', module: 'account', action: 'txlist' } export const GET_INTERNAL_TRANSACTIONS = { name: 'getInternalTransactions', module: 'account', action: 'txlistinternal' } export const GET_INTERNAL_TRANSACTIONS_BY_HASH = { name: 'getInternalTransactionsByHash', module: 'account', action: 'txlistinternal' } export const GET_INTERNAL_TRANSACTIONS_BY_BLOCK = { name: 'getInternalTransactionsByBlock', module: 'account', action: 'txlistinternal' } // Contracts export const GET_ABI = { name: 'getAbi', module: 'contract', action: 'getabi' } export const GET_CONTRACT_SOURCE_CODE = { name: 'getContractSourceCode', module: 'contract', action: 'getsourcecode' } // Transactions export const CHECK_CONTRACT_EXECUTION_STATUS = { name: 'getContractExecutionStatus', module: 'transaction', action: 'getstatus' } export const CHECK_TRANSACTION_RECEIPT_STATUS = { name: 'getTransactionReceiptStatus', module: 'transaction', action: 'gettxreceiptstatus' } // Tokens export const GET_ERC20_TOKEN_SUPPLY = { name: 'getErc20TokenSupply', module: 'stats', action: 'tokensupply' } export const GET_ERC20_TOKEN_BALANCE = { name: 'getErc20TokenBalance', module: 'account', action: 'tokenbalance' } // Stats export const GET_ETHER_PRICE = { name: 'getEtherPrice', module: 'stats', action: 'ethprice' } export const GET_ETHER_SUPPLY = { name: 'getEtherSupply', module: 'stats', action: 'ethsupply' } export const OPERATIONS = [ GET_ETHER_BALANCE, GET_MULTI_ETHER_BALANCE, GET_NORMAL_TRANSACTIONS, GET_INTERNAL_TRANSACTIONS, GET_INTERNAL_TRANSACTIONS_BY_HASH, GET_INTERNAL_TRANSACTIONS_BY_BLOCK, GET_ABI, GET_CONTRACT_SOURCE_CODE, CHECK_TRANSACTION_RECEIPT_STATUS, CHECK_CONTRACT_EXECUTION_STATUS, GET_ERC20_TOKEN_SUPPLY, GET_ERC20_TOKEN_BALANCE, GET_ETHER_PRICE, GET_ETHER_SUPPLY ] as const export const SORT_BY = [ { label: 'Desc', name: 'desc' }, { label: 'Asc', name: 'asc' } ] ================================================ FILE: packages/components/nodes/BEP20Function/BEP20Function.ts ================================================ import { ICommonObject, IDbCollection, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, IWallet, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import { BSCNetworks, chainIdLookup, getNetworkProvider, getNetworkProvidersList, NETWORK, networkExplorers, networkProviderCredentials, NETWORK_PROVIDER } from '../../src' import { ContractInterface, ethers } from 'ethers' import axios, { AxiosRequestConfig, Method } from 'axios' import { IToken } from '../Uniswap/nativeTokens' import { allowanceParameters, approveParameters, balanceOfParameters, BEP20Functions, transferFromParameters, withdrawParameters } from './helperFunctions' import IBEP20 from '../../src/abis/WBNB.json' import IWETH from '@uniswap/v2-periphery/build/IWETH.json' import { depositParameters } from '../ERC20Function/helperFunctions' class BEP20Function implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'BEP20 Function' this.name = 'BEP20Function' this.icon = 'bep20.png' this.type = 'action' this.category = 'Cryptocurrency' this.version = 1.0 this.description = 'Execute BEP20 function such as deposit, withdraw, get balance, etc' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Function', name: 'function', type: 'options', options: [...BEP20Functions] } ] as INodeParams[] this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...BSCNetworks] }, { label: 'Network Provider', name: 'networkProvider', type: 'asyncOptions', loadMethod: 'getNetworkProviders' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] this.inputParameters = [ { label: 'BEP20 Token', name: 'bep20Token', type: 'asyncOptions', description: 'BEP20 Token to send/transfer', loadMethod: 'getTokens' }, { label: 'Custom BEP20 Address', name: 'customBEP20TokenAddress', type: 'string', description: 'BEP20 Token Address', show: { 'inputParameters.bep20Token': ['customBEP20Address'] } }, ...approveParameters, ...allowanceParameters, ...balanceOfParameters, ...transferFromParameters, ...depositParameters, ...withdrawParameters, { label: 'Gas Limit', name: 'gasLimit', type: 'number', optional: true, placeholder: '100000', description: 'Maximum price you are willing to pay when sending a transaction', show: { 'actions.function': ['approve', 'transferFrom', 'deposit'] } }, { label: 'Max Fee per Gas', name: 'maxFeePerGas', type: 'number', optional: true, placeholder: '200', description: 'The maximum price (in wei) per unit of gas for transaction. See more', show: { 'actions.function': ['approve', 'transferFrom', 'deposit'] } }, { label: 'Max Priority Fee per Gas', name: 'maxPriorityFeePerGas', type: 'number', optional: true, placeholder: '5', description: 'The priority fee price (in wei) per unit of gas for transaction. See more', show: { 'actions.function': ['approve', 'transferFrom', 'deposit'] } } ] as INodeParams[] } loadMethods = { async getWallets(nodeData: INodeData, dbCollection?: IDbCollection): Promise { const returnData: INodeOptionsValue[] = [] try { if (dbCollection === undefined || !dbCollection || !dbCollection.Wallet) { return returnData } const wallets: IWallet[] = dbCollection.Wallet for (let i = 0; i < wallets.length; i += 1) { const wallet = wallets[i] const data = { label: `${wallet.name} (${wallet.network})`, name: JSON.stringify(wallet), description: wallet.address } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } }, async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK return getNetworkProvidersList(network) }, async getTokens(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://tokens.pancakeswap.finance/pancakeswap-extended.json` } const response = await axios(axiosConfig) const responseData = response.data let tokens: IToken[] = responseData.tokens // Add custom token const data = { label: `- Custom BEP20 Address -`, name: `customBEP20Address` } as INodeOptionsValue returnData.push(data) // Add other tokens tokens = tokens.filter((tkn) => tkn.chainId === chainIdLookup[network]) for (let i = 0; i < tokens.length; i += 1) { const token = tokens[i] const data = { label: `${token.name} (${token.symbol})`, name: `${token.address};${token.decimals}` } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } } } async run(nodeData: INodeData): Promise { const networksData = nodeData.networks const credentials = nodeData.credentials const actionsData = nodeData.actions const inputParametersData = nodeData.inputParameters if (networksData === undefined || inputParametersData === undefined || actionsData === undefined) { throw new Error('Required data missing') } try { const BEP20Function = actionsData.function as string const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') const owner = inputParametersData.owner as string const spender = inputParametersData.spender as string const amount = inputParametersData.amount as string const account = inputParametersData.account as string const from = inputParametersData.from as string const to = inputParametersData.to as string const bep20Token = inputParametersData.bep20Token as string const customBEP20TokenAddress = inputParametersData.customBEP20TokenAddress as string const contractAddress = bep20Token === 'customBEP20Address' ? customBEP20TokenAddress : bep20Token.split(';')[0] const contractInstance = new ethers.Contract(contractAddress, IBEP20, provider) const decimals = bep20Token === 'customBEP20Address' ? parseInt(await contractInstance.decimals(), 10) : parseInt(bep20Token.split(';').pop() || '0', 10) let returnItem = { function: BEP20Function, link: `${networkExplorers[network]}/address/${contractAddress}` } as any if (BEP20Function === 'allowance') { // allowance(address owner, address spender) → uint256 returnItem.result = await contractInstance.allowance(owner, spender) } else if (BEP20Function === 'approve') { // approve(address spender, uint256 amount) → bool const { txOption, wallet } = await getWalletSigner(inputParametersData, provider) const functionApproveAbi = ['function approve(address spender, uint256 amount) external returns (boolean)'] const contractInstance = new ethers.Contract(contractAddress, functionApproveAbi, wallet) const numberOfTokens = ethers.utils.parseUnits(amount, decimals) const tx = await contractInstance.approve(spender, numberOfTokens, txOption) const txReceipt = await tx.wait() returnItem = { function: BEP20Function, spender, amount, transactionHash: tx.hash, transactionReceipt: txReceipt as any, link: `${networkExplorers[network]}/tx/${tx.hash}` } } else if (BEP20Function === 'balanceOf') { // balanceOf(address account) → uint256 returnItem.result = ethers.utils.formatEther(await contractInstance.balanceOf(account)) } else if (BEP20Function === 'decimals') { // decimals() → uint8 returnItem.result = await contractInstance.decimals() } else if (BEP20Function === 'name') { // name() → string returnItem.result = await contractInstance.name() } else if (BEP20Function === 'symbol') { // symbol() → string returnItem.result = await contractInstance.symbol() } else if (BEP20Function === 'totalSupply') { // totalSupply() → uint256 returnItem.result = ethers.utils.formatEther(await contractInstance.totalSupply()) } else if (BEP20Function === 'transferFrom') { // transferFrom(address sender, address recipient, uint256 amount) → bool const { txOption, wallet } = await getWalletSigner(inputParametersData, provider) const functionTransferFromAbi = [ 'function transferFrom(address sender, address recipient, uint256 amount) external returns (boolean)' ] const contractInstance = new ethers.Contract(contractAddress, functionTransferFromAbi, wallet) const numberOfTokens = ethers.utils.parseUnits(amount, decimals) const tx = await contractInstance.transferFrom(from, to, numberOfTokens, txOption) const txReceipt = await tx.wait() returnItem = { function: BEP20Function, transferFrom: from, transferTo: to, amount, transactionHash: tx.hash, transactionReceipt: txReceipt as any, link: `${networkExplorers[network]}/tx/${tx.hash}` } } else if (BEP20Function === 'deposit') { const { txOption, wallet } = await getWalletSigner(inputParametersData, provider) const wrapEthContract = new ethers.Contract(contractAddress, IWETH['abi'] as ContractInterface, wallet) const numberOfTokens = ethers.utils.parseUnits(amount, decimals) const tx = await wrapEthContract.deposit({ ...txOption, value: numberOfTokens }) const txReceipt = await tx.wait() if (txReceipt.status === 0) throw new Error(`Failed to deposit BNB to ${contractAddress}`) returnItem = { function: BEP20Function, amount, transactionHash: tx.hash, transactionReceipt: txReceipt as any, link: `${networkExplorers[network]}/tx/${tx.hash}` } } else if (BEP20Function === 'withdraw') { const { wallet } = await getWalletSigner(inputParametersData, provider) const wrapEthContract = new ethers.Contract(contractAddress, IWETH['abi'] as ContractInterface, wallet) const numberOfTokens = ethers.utils.parseUnits(amount, decimals) const tx = await wrapEthContract.withdraw(numberOfTokens) const txReceipt = await tx.wait() if (txReceipt.status === 0) throw new Error(`Failed to withdraw BNB from ${contractAddress}`) returnItem = { function: BEP20Function, amount, transactionHash: tx.hash, transactionReceipt: txReceipt as any, link: `${networkExplorers[network]}/tx/${tx.hash}` } } return returnNodeExecutionData(returnItem) } catch (error) { throw handleErrorMessage(error) } } } const getWalletSigner = async ( inputParametersData: ICommonObject, provider: ethers.providers.JsonRpcProvider | ethers.providers.FallbackProvider ) => { const walletString = inputParametersData.wallet as string const walletDetails: IWallet = JSON.parse(walletString) const walletCredential = JSON.parse(walletDetails.walletCredential) const gasLimit = inputParametersData.gasLimit as number const maxFeePerGas = inputParametersData.maxFeePerGas as number const maxPriorityFeePerGas = inputParametersData.maxPriorityFeePerGas as number const wallet = new ethers.Wallet(walletCredential.privateKey as string, provider) const txOption = {} as any txOption.nonce = await provider.getTransactionCount(walletDetails.address) if (gasLimit) txOption.gasLimit = gasLimit if (maxFeePerGas) txOption.maxFeePerGas = maxFeePerGas if (maxPriorityFeePerGas) txOption.maxPriorityFeePerGas = maxPriorityFeePerGas return { txOption, wallet } } module.exports = { nodeClass: BEP20Function } ================================================ FILE: packages/components/nodes/BEP20Function/helperFunctions.ts ================================================ import { INodeOptionsValue, INodeParams } from '../../src' export const BEP20Functions = [ { name: 'deposit', label: 'Deposit', description: 'Deposit BNB into BEP20 token' }, { name: 'withdraw', label: 'Withdraw', description: 'Withdraw BNB from BEP20 token' }, { name: 'allowance', label: 'Get Allowance', description: 'Returns the remaining number of tokens that spender will be allowed to spend on behalf of owner through transferFrom. This is zero by default.' }, { name: 'approve', label: 'Approve', description: 'Sets amount as the allowance of spender over the caller’s tokens.' }, { name: 'balanceOf', label: 'Get Balance', description: 'Returns the amount of tokens owned by account' }, { name: 'decimals', label: 'Get BEP20 Decimals', description: 'Returns the decimals of BEP20' }, { name: 'name', label: 'Get BEP20 Name', description: 'Returns the name of BEP20' }, { name: 'symbol', label: 'Get BEP20 Symbol', description: 'Returns the symbol of BEP20' }, { name: 'totalSupply', label: 'Get BEP20 Total Supply', description: 'Returns the total supply of BEP20' }, { name: 'transferFrom', label: 'Transfer From', description: 'Moves amount tokens from sender to recipient using the allowance mechanism. Amount is then deducted from the caller’s allowance.' } ] as INodeOptionsValue[] export const allowanceParameters = [ { label: 'Owner Address', name: 'owner', type: 'string', show: { 'actions.function': ['allowance'] } }, { label: 'Spender Address', name: 'spender', type: 'string', show: { 'actions.function': ['allowance'] } } ] as INodeParams[] export const approveParameters = [ { label: 'Spender Address', name: 'spender', type: 'string', show: { 'actions.function': ['approve'] } }, { label: 'Amount', name: 'amount', type: 'number', show: { 'actions.function': ['approve'] } }, { label: 'Select Wallet', name: 'wallet', type: 'asyncOptions', description: 'Wallet to execute approve function', loadFromDbCollections: ['Wallet'], loadMethod: 'getWallets', show: { 'actions.function': ['approve'] } } ] as INodeParams[] export const balanceOfParameters = [ { label: 'Account Address', name: 'account', type: 'string', description: 'Account address to check for remaining amount', show: { 'actions.function': ['balanceOf'] } } ] as INodeParams[] export const transferFromParameters = [ { label: 'From Address', name: 'from', type: 'string', description: 'Account address to transfer the token', show: { 'actions.function': ['transferFrom'] } }, { label: 'To Address', name: 'to', type: 'string', description: 'Account address to receive the token', show: { 'actions.function': ['transferFrom'] } }, { label: 'Amount', name: 'amount', type: 'number', description: 'Amount of token transfer', show: { 'actions.function': ['transferFrom'] } }, { label: 'Select Wallet', name: 'wallet', type: 'asyncOptions', description: 'Wallet to move amount tokens from sender to recipient using the allowance mechanism', loadFromDbCollections: ['Wallet'], loadMethod: 'getWallets', show: { 'actions.function': ['transferFrom'] } } ] as INodeParams[] export const depositParameters = [ { label: 'Amount', name: 'amount', type: 'number', description: 'Amount of token to deposit', show: { 'actions.function': ['deposit'] } }, { label: 'Select Wallet', name: 'wallet', type: 'asyncOptions', description: 'Wallet to deposit ETH into ERC20 Token', loadFromDbCollections: ['Wallet'], loadMethod: 'getWallets', show: { 'actions.function': ['deposit'] } } ] as INodeParams[] export const withdrawParameters = [ { label: 'Amount', name: 'amount', type: 'number', description: 'Amount of token to withdraw', show: { 'actions.function': ['withdraw'] } }, { label: 'Select Wallet', name: 'wallet', type: 'asyncOptions', description: 'Withdraw ERC20 Token to this wallet', loadFromDbCollections: ['Wallet'], loadMethod: 'getWallets', show: { 'actions.function': ['withdraw'] } } ] as INodeParams[] ================================================ FILE: packages/components/nodes/BEP20Transfer/BEP20Transfer.ts ================================================ import { IDbCollection, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, IWallet, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import { BSCNetworks, chainIdLookup, functionTransferAbi, getNetworkProvider, getNetworkProvidersList, NETWORK, networkExplorers, networkProviderCredentials, NETWORK_PROVIDER } from '../../src' import { ethers } from 'ethers' import axios, { AxiosRequestConfig, Method } from 'axios' import { IToken } from '../Uniswap/nativeTokens' import IBEP20 from '../../src/abis/WBNB.json' class BEP20Transfer implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'BEP20 Transfer' this.name = 'BEP20Transfer' this.icon = 'bep20.png' this.type = 'action' this.category = 'Cryptocurrency' this.version = 1.0 this.description = 'Send/Transfer BEP20 to an address' this.incoming = 1 this.outgoing = 1 this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...BSCNetworks] }, { label: 'Network Provider', name: 'networkProvider', type: 'asyncOptions', loadMethod: 'getNetworkProviders' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] this.inputParameters = [ { label: 'BEP20 Token', name: 'bep20Token', type: 'asyncOptions', description: 'BEP20 Token to send/transfer', loadMethod: 'getTokens' }, { label: 'Custom BEP20 Address', name: 'customBEP20TokenAddress', type: 'string', description: 'BEP20 Token Address', show: { 'inputParameters.bep20Token': ['customBEP20Address'] } }, { label: 'Wallet To Transfer', name: 'wallet', type: 'asyncOptions', description: 'Wallet account to send/transfer BEP20', loadFromDbCollections: ['Wallet'], loadMethod: 'getWallets' }, { label: 'Address To Receive', name: 'address', type: 'string', default: '', description: 'Address to receive BEP20' }, { label: 'Amount', name: 'amount', type: 'number', description: 'Amount of BEP20 to transfer' }, { label: 'Gas Limit', name: 'gasLimit', type: 'number', optional: true, placeholder: '100000', description: 'Maximum price you are willing to pay when sending a transaction' }, { label: 'Max Fee per Gas', name: 'maxFeePerGas', type: 'number', optional: true, placeholder: '200', description: 'The maximum price (in wei) per unit of gas for transaction. See more' }, { label: 'Max Priority Fee per Gas', name: 'maxPriorityFeePerGas', type: 'number', optional: true, placeholder: '5', description: 'The priority fee price (in wei) per unit of gas for transaction. See more' } ] as INodeParams[] } loadMethods = { async getWallets(nodeData: INodeData, dbCollection?: IDbCollection): Promise { const returnData: INodeOptionsValue[] = [] try { if (dbCollection === undefined || !dbCollection || !dbCollection.Wallet) { return returnData } const wallets: IWallet[] = dbCollection.Wallet for (let i = 0; i < wallets.length; i += 1) { const wallet = wallets[i] const data = { label: `${wallet.name} (${wallet.network})`, name: JSON.stringify(wallet), description: wallet.address } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } }, async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK return getNetworkProvidersList(network) }, async getTokens(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://tokens.pancakeswap.finance/pancakeswap-extended.json` } const response = await axios(axiosConfig) const responseData = response.data let tokens: IToken[] = responseData.tokens // Add custom token const data = { label: `- Custom BEP20 Address -`, name: `customBEP20Address` } as INodeOptionsValue returnData.push(data) // Add other tokens tokens = tokens.filter((tkn) => tkn.chainId === chainIdLookup[network]) for (let i = 0; i < tokens.length; i += 1) { const token = tokens[i] const data = { label: `${token.name} (${token.symbol})`, name: `${token.address};${token.decimals}` } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } } } async run(nodeData: INodeData): Promise { const networksData = nodeData.networks const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (networksData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } try { const walletString = inputParametersData.wallet as string const walletDetails: IWallet = JSON.parse(walletString) const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') // Get wallet instance const walletCredential = JSON.parse(walletDetails.walletCredential) const wallet = new ethers.Wallet(walletCredential.privateKey as string, provider) const address = inputParametersData.address as string const amount = inputParametersData.amount as string const bep20Token = inputParametersData.bep20Token as string const customBEP20TokenAddress = inputParametersData.customBEP20TokenAddress as string const gasLimit = inputParametersData.gasLimit as number const maxFeePerGas = inputParametersData.maxFeePerGas as number const maxPriorityFeePerGas = inputParametersData.maxPriorityFeePerGas as number const contractAddress = bep20Token === 'customBEP20Address' ? customBEP20TokenAddress : bep20Token.split(';')[0] const contractInstance = new ethers.Contract(contractAddress, IBEP20, wallet) const decimals = bep20Token === 'customBEP20Address' ? parseInt(await contractInstance.decimals(), 10) : parseInt(bep20Token.split(';').pop() || '0', 10) const numberOfTokens = ethers.utils.parseUnits(amount, decimals) // Send token const nonce = await provider.getTransactionCount(walletDetails.address) const txOption = { nonce } as any if (gasLimit) txOption.gasLimit = gasLimit if (maxFeePerGas) txOption.maxFeePerGas = maxFeePerGas if (maxPriorityFeePerGas) txOption.maxPriorityFeePerGas = maxPriorityFeePerGas const contractInstanceForTransfer = new ethers.Contract(contractAddress, functionTransferAbi, wallet) const tx = await contractInstanceForTransfer.transfer(address, numberOfTokens, txOption) const txReceipt = await tx.wait() const returnItem = { transferFrom: wallet.address, transferTo: address, amount, transactionHash: tx.hash, transactionReceipt: txReceipt as any, link: `${networkExplorers[network]}/tx/${tx.hash}` } return returnNodeExecutionData(returnItem) } catch (error) { throw handleErrorMessage(error) } } } module.exports = { nodeClass: BEP20Transfer } ================================================ FILE: packages/components/nodes/BEP20TransferTrigger/BEP20TransferTrigger.ts ================================================ import { ethers, utils } from 'ethers' import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams, IProviders, NodeType } from '../../src/Interface' import { returnNodeExecutionData } from '../../src/utils' import EventEmitter from 'events' import { networkExplorers, BSCNetworks, binanceNetworkProviders, NETWORK_PROVIDER, NETWORK, getNetworkProvider, networkProviderCredentials, chainIdLookup } from '../../src/ChainNetwork' import axios, { AxiosRequestConfig, Method } from 'axios' import { IToken } from '../PancakeSwap/extendedTokens' import IBEP20 from '../../src/abis/WBNB.json' class BEP20TransferTrigger extends EventEmitter implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] providers: IProviders constructor() { super() this.label = 'BEP20 Transfer Trigger' this.name = 'BEP20TransferTrigger' this.icon = 'bep20.png' this.type = 'trigger' this.category = 'Cryptocurrency' this.version = 1.1 this.description = 'Triggers whenever a BEP20 transfer event happened' this.incoming = 0 this.outgoing = 1 this.providers = {} this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...BSCNetworks], default: 'bsc' }, { label: 'Network Provider', name: 'networkProvider', type: 'options', options: [...binanceNetworkProviders], default: 'binance' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] this.inputParameters = [ { label: 'BEP20 Token', name: 'bep20Token', type: 'asyncOptions', description: 'BEP20 Token to send/transfer', loadMethod: 'getTokens', default: 'anyBEP20Address' }, { label: 'Custom BEP20 Address', name: 'customBEP20TokenAddress', type: 'string', description: 'BEP20 Token Address', show: { 'inputParameters.bep20Token': ['customBEP20Address'] } }, { label: 'Direction', name: 'direction', type: 'options', options: [ { label: 'From', name: 'from', description: 'Transfer from wallet address' }, { label: 'To', name: 'to', description: 'Transfer to wallet address' }, { label: 'Both From and To', name: 'fromTo', description: 'Transfer from a wallet address to another wallet address' } ], default: '' }, { label: 'From Wallet Address', name: 'fromAddress', type: 'string', default: '', show: { 'inputParameters.direction': ['from', 'fromTo'] } }, { label: 'To Wallet Address', name: 'toAddress', type: 'string', default: '', show: { 'inputParameters.direction': ['to', 'fromTo'] } } ] as INodeParams[] } loadMethods = { async getTokens(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://tokens.pancakeswap.finance/pancakeswap-extended.json` } const response = await axios(axiosConfig) const responseData = response.data let tokens: IToken[] = responseData.tokens // Add any token const anyData = { label: `- Any BEP20 Token -`, name: `anyBEP20Address` } as INodeOptionsValue returnData.push(anyData) // Add custom token const data = { label: `- Custom BEP20 Address -`, name: `customBEP20Address` } as INodeOptionsValue returnData.push(data) // Add other tokens tokens = tokens.filter((tkn) => tkn.chainId === chainIdLookup[network]) for (let i = 0; i < tokens.length; i += 1) { const token = tokens[i] const data = { label: `${token.name} (${token.symbol})`, name: `${token.address};${token.decimals}` } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } } } async runTrigger(nodeData: INodeData): Promise { const networksData = nodeData.networks const inputParametersData = nodeData.inputParameters const credentials = nodeData.credentials if (networksData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') const emitEventKey = nodeData.emitEventKey as string const fromAddress = (inputParametersData.fromAddress as string) || null const toAddress = (inputParametersData.toAddress as string) || null const bep20Token = inputParametersData.bep20Token as string const customBEP20TokenAddress = inputParametersData.customBEP20TokenAddress as string const filter = { topics: [ utils.id('Transfer(address,address,uint256)'), fromAddress ? utils.hexZeroPad(fromAddress, 32) : null, toAddress ? utils.hexZeroPad(toAddress, 32) : null ] } as any if (bep20Token !== 'anyBEP20Address') { filter['address'] = bep20Token === 'customBEP20Address' ? customBEP20TokenAddress : bep20Token.split(';')[0] } provider.on(filter, async (log: any) => { const txHash = log.transactionHash const contractInstance = new ethers.Contract(log.address, IBEP20, provider) /* events are empty at the moment const iface = new ethers.utils.Interface(eventTransferAbi); const logs = await provider.getLogs(filter); const events = logs.map((log) => iface.parseLog(log)); const fromWallet = events.length ? events[0].args[0] : ''; const toWallet = events.length ? events[0].args[1] : ''; const value: BigNumber = events.length ? events[0].args[2] : ''; */ //BEP20 has 3 topics length if (log.topics.length === 3) { const returnItem = {} as ICommonObject const name = await contractInstance.name() const symbol = await contractInstance.symbol() //const decimals = await contractInstance.decimals(); //const amount = utils.formatUnits(value.toString(), decimals); returnItem['Token Name'] = name returnItem['Token Symbol'] = symbol returnItem['Token Address'] = log.address if (fromAddress) returnItem['From Wallet'] = fromAddress if (toAddress) returnItem['To Wallet'] = toAddress //returnItem['Amount Transfered'] = parseFloat(amount); returnItem['txHash'] = txHash returnItem['explorerLink'] = `${networkExplorers[network]}/tx/${txHash}` this.emit(emitEventKey, returnNodeExecutionData(returnItem)) } }) this.providers[emitEventKey] = { provider, filter } } async removeTrigger(nodeData: INodeData): Promise { const emitEventKey = nodeData.emitEventKey as string if (Object.prototype.hasOwnProperty.call(this.providers, emitEventKey)) { const provider = this.providers[emitEventKey].provider const filter = this.providers[emitEventKey].filter provider.off(filter) this.removeAllListeners(emitEventKey) } } } module.exports = { nodeClass: BEP20TransferTrigger } ================================================ FILE: packages/components/nodes/BNBBalanceTrigger/BNBBalanceTrigger.ts ================================================ import { CronJob } from 'cron' import { BigNumber, utils } from 'ethers' import { ICronJobs, INode, INodeData, INodeParams, NodeType } from '../../src/Interface' import { returnNodeExecutionData } from '../../src/utils' import EventEmitter from 'events' import { binanceNetworkProviders, BSCNetworks, getNetworkProvider, NETWORK, networkExplorers, networkProviderCredentials, NETWORK_PROVIDER } from '../../src/ChainNetwork' class BNBBalanceTrigger extends EventEmitter implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] cronJobs: ICronJobs constructor() { super() this.label = 'BNB Balance Trigger' this.name = 'BNBBalanceTrigger' this.icon = 'bnb.svg' this.type = 'trigger' this.category = 'Cryptocurrency' this.version = 1.0 this.description = 'Triggers whenever BNB balance in wallet changes' this.incoming = 0 this.outgoing = 1 this.cronJobs = {} this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...BSCNetworks], default: 'bsc' }, { label: 'Network Provider', name: 'networkProvider', type: 'options', options: [...binanceNetworkProviders], default: 'binance' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] this.inputParameters = [ { label: 'Wallet Address', name: 'address', type: 'string', default: '' }, { label: 'Trigger Condition', name: 'triggerCondition', type: 'options', options: [ { label: 'When balance increased', name: 'increase' }, { label: 'When balance decreased', name: 'decrease' } ], default: 'increase' }, { label: 'Polling Time', name: 'pollTime', type: 'options', options: [ { label: 'Every 15 secs', name: '15s' }, { label: 'Every 30 secs', name: '30s' }, { label: 'Every 1 min', name: '1min' }, { label: 'Every 5 min', name: '5min' }, { label: 'Every 10 min', name: '10min' } ], default: '30s' } ] as INodeParams[] } async runTrigger(nodeData: INodeData): Promise { const networksData = nodeData.networks const inputParametersData = nodeData.inputParameters const credentials = nodeData.credentials if (networksData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') const emitEventKey = nodeData.emitEventKey as string const address = (inputParametersData.address as string) || '' const pollTime = (inputParametersData.pollTime as string) || '30s' const triggerCondition = (inputParametersData.triggerCondition as string) || 'increase' const cronTimes: string[] = [] if (pollTime === '15s') { cronTimes.push(`*/15 * * * * *`) } else if (pollTime === '30s') { cronTimes.push(`*/30 * * * * *`) } else if (pollTime === '1min') { cronTimes.push(`*/1 * * * *`) } else if (pollTime === '5min') { cronTimes.push(`*/5 * * * *`) } else if (pollTime === '10min') { cronTimes.push(`*/10 * * * *`) } let lastBalance: BigNumber = await provider.getBalance(address) const executeTrigger = async () => { const newBalance: BigNumber = await provider.getBalance(address) if (!newBalance.eq(lastBalance)) { if (triggerCondition === 'increase' && newBalance.gt(lastBalance)) { const balanceInBNB = utils.formatEther(BigNumber.from(newBalance.toString())) const diffInBNB = newBalance.sub(lastBalance) const returnItem = { newBalance: `${balanceInBNB} BNB`, lastBalance: `${utils.formatEther(BigNumber.from(lastBalance.toString()))} BNB`, difference: `${utils.formatEther(BigNumber.from(diffInBNB.toString()))} BNB`, explorerLink: `${networkExplorers[network]}/address/${address}`, triggerCondition: 'BNB balance increase' } lastBalance = newBalance this.emit(emitEventKey, returnNodeExecutionData(returnItem)) } else if (triggerCondition === 'decrease' && newBalance.lt(lastBalance)) { const balanceInBNB = utils.formatEther(BigNumber.from(newBalance.toString())) const diffInBNB = lastBalance.sub(newBalance) const returnItem = { newBalance: `${balanceInBNB} BNB`, lastBalance: `${utils.formatEther(BigNumber.from(lastBalance.toString()))} BNB`, difference: `${utils.formatEther(BigNumber.from(diffInBNB.toString()))} BNB`, explorerLink: `${networkExplorers[network]}/address/${address}`, triggerCondition: 'BNB balance decrease' } lastBalance = newBalance this.emit(emitEventKey, returnNodeExecutionData(returnItem)) } else { lastBalance = newBalance } } } // Start the cron-jobs if (Object.prototype.hasOwnProperty.call(this.cronJobs, emitEventKey)) { for (const cronTime of cronTimes) { // Automatically start the cron job this.cronJobs[emitEventKey].push(new CronJob(cronTime, executeTrigger, undefined, true)) } } else { for (const cronTime of cronTimes) { // Automatically start the cron job this.cronJobs[emitEventKey] = [new CronJob(cronTime, executeTrigger, undefined, true)] } } } async removeTrigger(nodeData: INodeData): Promise { const emitEventKey = nodeData.emitEventKey as string if (Object.prototype.hasOwnProperty.call(this.cronJobs, emitEventKey)) { const cronJobs = this.cronJobs[emitEventKey] for (const cronJob of cronJobs) { cronJob.stop() } this.removeAllListeners(emitEventKey) } } } module.exports = { nodeClass: BNBBalanceTrigger } ================================================ FILE: packages/components/nodes/BNBTransfer/BNBTransfer.ts ================================================ import { IDbCollection, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, IWallet, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import { BSCNetworks, getNetworkProvider, getNetworkProvidersList, NETWORK, networkExplorers, networkProviderCredentials, NETWORK_PROVIDER } from '../../src' import { ethers } from 'ethers' class BNBTransfer implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'BNB Transfer' this.name = 'BNBTransfer' this.icon = 'bnb.svg' this.type = 'action' this.category = 'Cryptocurrency' this.version = 1.0 this.description = 'Send/Transfer BNB to an address' this.incoming = 1 this.outgoing = 1 this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...BSCNetworks] }, { label: 'Network Provider', name: 'networkProvider', type: 'asyncOptions', loadMethod: 'getNetworkProviders' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] this.inputParameters = [ { label: 'Wallet To Transfer', name: 'wallet', type: 'asyncOptions', description: 'Wallet account to send/transfer BNB', loadFromDbCollections: ['Wallet'], loadMethod: 'getWallets' }, { label: 'Address To Receive', name: 'address', type: 'string', default: '', description: 'Address to receive BNB' }, { label: 'Amount', name: 'amount', type: 'number', description: 'Amount of BNB to transfer' }, { label: 'Gas Limit', name: 'gasLimit', type: 'number', optional: true, placeholder: '100000', description: 'Maximum price you are willing to pay when sending a transaction' }, { label: 'Max Fee per Gas', name: 'maxFeePerGas', type: 'number', optional: true, placeholder: '200', description: 'The maximum price (in wei) per unit of gas for transaction. See more' }, { label: 'Max Priority Fee per Gas', name: 'maxPriorityFeePerGas', type: 'number', optional: true, placeholder: '5', description: 'The priority fee price (in wei) per unit of gas for transaction. See more' } ] as INodeParams[] } loadMethods = { async getWallets(nodeData: INodeData, dbCollection?: IDbCollection): Promise { const returnData: INodeOptionsValue[] = [] try { if (dbCollection === undefined || !dbCollection || !dbCollection.Wallet) { return returnData } const wallets: IWallet[] = dbCollection.Wallet for (let i = 0; i < wallets.length; i += 1) { const wallet = wallets[i] const data = { label: `${wallet.name} (${wallet.network})`, name: JSON.stringify(wallet), description: wallet.address } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } }, async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK return getNetworkProvidersList(network) } } async run(nodeData: INodeData): Promise { const networksData = nodeData.networks const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (networksData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } try { const walletString = inputParametersData.wallet as string const walletDetails: IWallet = JSON.parse(walletString) const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') // Get wallet instance const walletCredential = JSON.parse(walletDetails.walletCredential) const wallet = new ethers.Wallet(walletCredential.privateKey as string, provider) const address = inputParametersData.address as string const amount = inputParametersData.amount as string const gasLimit = inputParametersData.gasLimit as number const maxFeePerGas = inputParametersData.maxFeePerGas as number const maxPriorityFeePerGas = inputParametersData.maxPriorityFeePerGas as number // Send token const nonce = await provider.getTransactionCount(walletDetails.address) const txOption = { nonce } as any if (gasLimit) txOption.gasLimit = gasLimit if (maxFeePerGas) txOption.maxFeePerGas = maxFeePerGas if (maxPriorityFeePerGas) txOption.maxPriorityFeePerGas = maxPriorityFeePerGas const tx = await wallet.sendTransaction({ to: address, value: ethers.utils.parseEther(amount), ...txOption }) const txReceipt = await tx.wait() const returnItem = { transferFrom: wallet.address, transferTo: address, amount, transactionHash: tx.hash, transactionReceipt: txReceipt as any, link: `${networkExplorers[network]}/tx/${tx.hash}` } return returnNodeExecutionData(returnItem) } catch (error) { throw handleErrorMessage(error) } } } module.exports = { nodeClass: BNBTransfer } ================================================ FILE: packages/components/nodes/Binance/BinancePrivate.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, NodeType } from '../../src/Interface' import { notEmptyRegex, handleErrorMessage, numberOrExpressionRegex, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' import axios, { AxiosRequestConfig, AxiosRequestHeaders, Method } from 'axios' import { createHmac } from 'crypto' class BinancePrivate implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Binance Private' this.name = 'binancePrivate' this.icon = 'binance-logo.svg' this.type = 'action' this.category = 'Centralized Exchange' this.version = 1.0 this.description = 'Binance Private APIs that require API and Secret keys' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Network', name: 'network', type: 'options', description: 'Network to execute API: Test or Live', options: [ { label: 'TEST', name: 'test', description: 'Test network: https://testnet.binance.vision/' }, { label: 'LIVE', name: 'live', description: 'Live network: https://api.binance.com/' } ], default: 'test' }, { label: 'Operation', name: 'operation', type: 'options', options: [ { label: 'Send New Order', name: 'postNewOrder', description: 'Send in a new order.' }, { label: 'Test New Order', name: 'postTestNewOrder', description: 'Test new order creation. Creates and validates a new order but does not send it into the matching engine.' }, { label: 'Query Order', name: 'getOrder', description: `Check an order's status.` }, { label: 'Cancel Order', name: 'delOrder', description: `Cancel an active order.` }, { label: 'Cancel All Open Orders on a Symbol', name: 'delOpenOrders', description: `Cancels all active orders on a symbol. This includes OCO orders.` }, { label: 'Query Current Open Orders', name: 'getOpenOrders', description: `Get all open orders on a symbol.` }, { label: 'Query All Orders', name: 'getAllOrders', description: `Get all account orders; active, canceled, or filled.` }, { label: 'Query Account Information', name: 'getAccountInformation', description: `Get current account information.` }, { label: 'Query Account Trade List', name: 'getMyTrades', description: `Get trades for a specific account and symbol.` }, { label: 'Query Current Order Count Usage', name: 'getOrderCountUsage', description: `Displays the user's current order count usage for all intervals.` } ], default: 'getAccountInformation' } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Binance API Key', name: 'binanceApi' } ], default: 'binanceApi' } ] as INodeParams[] this.inputParameters = [ { label: 'Symbol', name: 'symbol', type: 'asyncOptions', loadMethod: 'getSupportedSymbols', show: { 'actions.operation': [ 'postNewOrder', 'postTestNewOrder', 'getOrder', 'delOrder', 'delOpenOrders', 'getOpenOrders', 'getAllOrders', 'getMyTrades' ] } }, { label: 'Order Side', name: 'side', type: 'options', options: [ { label: 'BUY', name: 'BUY' }, { label: 'SELL', name: 'SELL' } ], show: { 'actions.operation': ['postNewOrder', 'postTestNewOrder'] } }, { label: 'Order Type', name: 'type', type: 'options', options: [ { label: 'LIMIT', name: 'LIMIT' }, { label: 'MARKET', name: 'MARKET' }, { label: 'STOP_LOSS', name: 'STOP_LOSS' }, { label: 'STOP_LOSS_LIMIT', name: 'STOP_LOSS_LIMIT' }, { label: 'TAKE_PROFIT', name: 'TAKE_PROFIT' }, { label: 'TAKE_PROFIT_LIMIT', name: 'TAKE_PROFIT_LIMIT' }, { label: 'LIMIT_MAKER', name: 'LIMIT_MAKER' } ], default: 'LIMIT', show: { 'actions.operation': ['postNewOrder', 'postTestNewOrder'] } }, { label: 'Time in Force', name: 'timeInForce', type: 'options', description: 'This sets how long an order will be active before expiration.', options: [ { label: 'Good Til Canceled', name: 'GTC', description: 'An order will be on the book unless the order is canceled.' }, { label: 'Immediate Or Cancel', name: 'IOC', description: 'An order will try to fill the order as much as it can before the order expires.' }, { label: 'Fill or Kill', name: 'FOK', description: 'An order will expire if the full order cannot be filled upon execution.' } ], optional: { 'inputParameters.type': ['MARKET', 'STOP_LOSS', 'TAKE_PROFIT', 'LIMIT_MAKER'] }, show: { 'actions.operation': ['postNewOrder', 'postTestNewOrder'] } }, { label: 'Quantity', name: 'quantity', type: 'number', description: 'For MARKET order type, Quantity or Quote Order Quantity is mandatory.', optional: { 'inputParameters.type': ['MARKET'], 'inputParameters.quoteOrderQty': numberOrExpressionRegex }, show: { 'actions.operation': ['postNewOrder', 'postTestNewOrder'] } }, { label: 'Quote Order Quantity', name: 'quoteOrderQty', type: 'number', description: 'Specifies the amount the user wants to spend (when buying) or receive (when selling). For MARKET order type, Quantity or Quote Order Quantity is mandatory.', optional: { 'inputParameters.quantity': numberOrExpressionRegex }, show: { 'actions.operation': ['postNewOrder', 'postTestNewOrder'], 'inputParameters.type': ['MARKET'] } }, { label: 'Price', name: 'price', type: 'number', optional: { 'inputParameters.type': ['MARKET', 'STOP_LOSS', 'TAKE_PROFIT'] }, show: { 'actions.operation': ['postNewOrder', 'postTestNewOrder'] } }, { label: 'New Client Order Id', name: 'newClientOrderId', description: 'A unique id among open orders. Automatically generated if not sent. Orders with the same newClientOrderID can be accepted only when the previous one is filled, otherwise the order will be rejected.', type: 'string', optional: true, show: { 'actions.operation': ['postNewOrder', 'postTestNewOrder'] } }, { label: 'Stop Price', name: 'stopPrice', type: 'number', description: 'For [STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, TAKE_PROFIT_LIMIT] order type, Stop Price or Trailing Delta is mandatory.', optional: { 'inputParameters.trailingDelta': numberOrExpressionRegex }, show: { 'inputParameters.type': ['STOP_LOSS', 'STOP_LOSS_LIMIT', 'TAKE_PROFIT', 'TAKE_PROFIT_LIMIT'], 'actions.operation': ['postNewOrder', 'postTestNewOrder'] } }, { label: 'Trailing Delta', name: 'trailingDelta', type: 'number', description: 'For [STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, TAKE_PROFIT_LIMIT] order type, Stop Price or Trailing Delta is mandatory.', optional: { 'inputParameters.stopPrice': numberOrExpressionRegex }, show: { 'inputParameters.type': ['STOP_LOSS', 'STOP_LOSS_LIMIT', 'TAKE_PROFIT', 'TAKE_PROFIT_LIMIT'], 'actions.operation': ['postNewOrder', 'postTestNewOrder'] } }, { label: 'Iceberg Quantity', name: 'icebergQty', type: 'number', optional: true, show: { 'inputParameters.type': ['LIMIT', 'STOP_LOSS_LIMIT', 'TAKE_PROFIT_LIMIT'], 'actions.operation': ['postNewOrder', 'postTestNewOrder'] } }, { label: 'New Order Response Type', name: 'newOrderRespType', type: 'options', description: 'Set the response JSON. MARKET and LIMIT order types default to FULL, all other orders default to ACK.', options: [ { label: 'ACK', name: 'ACK' }, { label: 'RESULT', name: 'RESULT' }, { label: 'FULL', name: 'FULL' } ], optional: true, show: { 'actions.operation': ['postNewOrder', 'postTestNewOrder'] } }, { label: 'Order Id', name: 'orderId', type: 'number', description: 'Order Id or Orig Client Order Id is mandatory.', optional: { 'inputParameters.origClientOrderId': notEmptyRegex }, show: { 'actions.operation': ['getOrder', 'delOrder'] } }, { label: 'Orig Client Order Id', name: 'origClientOrderId', type: 'string', description: 'Order Id or Orig Client Order Id is mandatory.', optional: { 'inputParameters.orderId': numberOrExpressionRegex }, show: { 'actions.operation': ['getOrder', 'delOrder'] } }, { label: 'Order Id', name: 'orderId', type: 'number', description: 'Get orders >= Order Id. Otherwise most recent orders are returned. Not needed if Start Time and/or End Time provided.', optional: true, show: { 'actions.operation': ['getAllOrders', 'getMyTrades'] } }, { label: 'Start Time', name: 'startTime', type: 'date', optional: true, show: { 'actions.operation': ['getAllOrders', 'getMyTrades'] } }, { label: 'End Time', name: 'endTime', type: 'date', optional: true, show: { 'actions.operation': ['getAllOrders', 'getMyTrades'] } }, { label: 'From Id', name: 'fromId', type: 'number', optional: true, description: 'TradeId to fetch from. Default gets most recent trades.', show: { 'actions.operation': ['getMyTrades'] } }, { label: 'Limit', name: 'limit', type: 'number', default: 500, optional: true, show: { 'actions.operation': ['getAllOrders', 'getMyTrades'] } } ] } loadMethods = { async getSupportedSymbols(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const actionData = nodeData.actions let apiUrl = '' if (actionData !== undefined && (actionData.network as string) === 'test') { apiUrl = 'https://testnet.binance.vision/api' } else { apiUrl = 'https://api.binance.com/api' } const axiosConfig: AxiosRequestConfig = { method: 'GET', url: `${apiUrl}/v3/exchangeInfo`, headers: { 'Content-Type': 'application/json' } } const response = await axios(axiosConfig) const responseData = response.data for (const s of responseData['symbols']) { returnData.push({ label: s.symbol, name: s.symbol }) } return returnData } } async run(nodeData: INodeData): Promise { const actionData = nodeData.actions const inputParametersData = nodeData.inputParameters const credentials = nodeData.credentials if (actionData === undefined || inputParametersData === undefined || credentials === undefined) { throw new Error('Required data missing') } const network = actionData.network as string const operation = actionData.operation as string const apiKey = credentials.apiKey as string const secretKey = credentials.secretKey as string const returnData: ICommonObject[] = [] let responseData: any // tslint:disable-line: no-any let apiUrl = '' if (network === 'test') { apiUrl = 'https://testnet.binance.vision/api' } else if (network === 'live') { apiUrl = 'https://api.binance.com/api' } let url = '' const queryParameters: ICommonObject = {} const queryBody: ICommonObject = {} let method: Method = 'GET' const headers: AxiosRequestHeaders = { 'Content-Type': 'application/json' } try { if (operation === 'postNewOrder' || operation === 'postTestNewOrder') { const symbol = inputParametersData.symbol as string const side = inputParametersData.side as string const type = inputParametersData.type as string const timeInForce = inputParametersData.timeInForce as string const quantity = inputParametersData.quantity as number const quoteOrderQty = inputParametersData.quoteOrderQty as number const price = inputParametersData.price as number const newClientOrderId = inputParametersData.newClientOrderId as string const stopPrice = inputParametersData.stopPrice as number const trailingDelta = inputParametersData.trailingDelta as number const icebergQty = inputParametersData.icebergQty as number const newOrderRespType = inputParametersData.newOrderRespType as string const timestamp = Date.now() queryParameters['symbol'] = symbol queryParameters['side'] = side queryParameters['type'] = type if (timeInForce) queryParameters['timeInForce'] = timeInForce if (quantity) queryParameters['quantity'] = quantity if (quoteOrderQty) queryParameters['quoteOrderQty'] = quoteOrderQty if (price) queryParameters['price'] = price if (newClientOrderId) queryParameters['newClientOrderId'] = newClientOrderId if (stopPrice) queryParameters['stopPrice'] = stopPrice if (trailingDelta) queryParameters['trailingDelta'] = trailingDelta if (icebergQty) queryParameters['icebergQty'] = icebergQty if (newOrderRespType) queryParameters['newOrderRespType'] = newOrderRespType queryParameters['recvWindow'] = 5000 queryParameters['timestamp'] = timestamp const serializedQueryParams = serializeQueryParams(queryParameters) const signature = createHmac('sha256', secretKey).update(serializedQueryParams).digest('hex') queryParameters['signature'] = signature method = 'POST' headers['X-MBX-APIKEY'] = apiKey url = `${apiUrl}/v3/order` if (operation === 'postTestNewOrder') url += '/test' } else if (operation === 'getOrder' || operation === 'delOrder') { const symbol = inputParametersData.symbol as string const orderId = inputParametersData.orderId as number const origClientOrderId = inputParametersData.origClientOrderId as string const timestamp = Date.now() queryParameters['symbol'] = symbol if (orderId) queryParameters['orderId'] = orderId if (origClientOrderId) queryParameters['origClientOrderId'] = origClientOrderId queryParameters['recvWindow'] = 5000 queryParameters['timestamp'] = timestamp const serializedQueryParams = serializeQueryParams(queryParameters) const signature = createHmac('sha256', secretKey).update(serializedQueryParams).digest('hex') queryParameters['signature'] = signature method = operation === 'delOrder' ? 'DELETE' : 'GET' headers['X-MBX-APIKEY'] = apiKey url = `${apiUrl}/v3/order` } else if (operation === 'getOpenOrders' || operation === 'delOpenOrders') { const symbol = inputParametersData.symbol as string const timestamp = Date.now() queryParameters['symbol'] = symbol queryParameters['recvWindow'] = 5000 queryParameters['timestamp'] = timestamp const serializedQueryParams = serializeQueryParams(queryParameters) const signature = createHmac('sha256', secretKey).update(serializedQueryParams).digest('hex') queryParameters['signature'] = signature method = operation === 'delOpenOrders' ? 'DELETE' : 'GET' headers['X-MBX-APIKEY'] = apiKey url = `${apiUrl}/v3/openOrders` } else if (operation === 'getAllOrders' || operation === 'getMyTrades') { const symbol = inputParametersData.symbol as string const orderId = inputParametersData.orderId as number const startTime = Date.parse(inputParametersData.startTime as string) const endTime = Date.parse(inputParametersData.endTime as string) const limit = inputParametersData.limit as number const fromId = inputParametersData.fromId as number const timestamp = Date.now() queryParameters['symbol'] = symbol if (orderId) queryParameters['orderId'] = orderId if (startTime) queryParameters['startTime'] = startTime if (endTime) queryParameters['endTime'] = endTime if (limit) queryParameters['limit'] = limit if (fromId) queryParameters['fromId'] = fromId queryParameters['recvWindow'] = 5000 queryParameters['timestamp'] = timestamp const serializedQueryParams = serializeQueryParams(queryParameters) const signature = createHmac('sha256', secretKey).update(serializedQueryParams).digest('hex') queryParameters['signature'] = signature method = 'GET' headers['X-MBX-APIKEY'] = apiKey const endpoint = operation === 'getMyTrades' ? 'myTrades' : 'allOrders' url = `${apiUrl}/v3/${endpoint}` } else if (operation === 'getAccountInformation' || operation === 'getOrderCountUsage') { const timestamp = Date.now() queryParameters['recvWindow'] = 5000 queryParameters['timestamp'] = timestamp const serializedQueryParams = serializeQueryParams(queryParameters) const signature = createHmac('sha256', secretKey).update(serializedQueryParams).digest('hex') queryParameters['signature'] = signature method = 'GET' headers['X-MBX-APIKEY'] = apiKey const endpoint = operation === 'getAccountInformation' ? 'account' : 'rateLimit/order' url = `${apiUrl}/v3/${endpoint}` } const axiosConfig: AxiosRequestConfig = { method, url, params: queryParameters, paramsSerializer: (params) => serializeQueryParams(params), headers } if (Object.keys(queryBody).length > 0) { axiosConfig.data = queryBody } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: BinancePrivate } ================================================ FILE: packages/components/nodes/Binance/BinancePublic.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' class BinancePublic implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Binance Public' this.name = 'binancePublic' this.type = 'action' this.icon = 'binance-logo.svg' this.category = 'Centralized Exchange' this.description = 'Binance Public API' this.version = 1.0 this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Network', name: 'network', type: 'options', description: 'Network to execute API: Test or Real', options: [ { label: 'TEST', name: 'test', description: 'Test network: https://testnet.binance.vision/' }, { label: 'LIVE', name: 'live', description: 'Live network: https://api.binance.com/' } ], default: 'test' }, { label: 'Operation', name: 'operation', type: 'options', options: [ { label: 'Get Order Book', name: 'getOrderBook', description: 'Get order book.' }, { label: 'Get Exchange Information', name: 'getExchangeInfo', description: 'Get current exchange trading rules and symbol information.' }, { label: 'Get Recent Trades List', name: 'getRecentTradesList', description: 'Get recent trades.' }, { label: 'Get Compressed, Aggregate Trades', name: 'getAggTrades', description: 'Get compressed, aggregate trades. Trades that fill at the time, from the same taker order, with the same price will have the quantity aggregated.' }, { label: 'Get Kline/Candlestick data', name: 'getKlines', description: 'Kline/candlestick bars for a symbol. Klines are uniquely identified by their open time.' }, { label: 'Get Current Average Price', name: 'getAvgPrice', description: 'Current average price for a symbol.' }, { label: 'Get 24hr Ticker Price Change Statistics', name: 'get24hrTickerPrice', description: '24 hour rolling window price change statistics.' }, { label: 'Get Symbol Price Ticker', name: 'getTickerPrice', description: 'Latest price for a symbol or symbols.' }, { label: 'Get Symbol Order Book Ticker', name: 'getBookTicker', description: 'Best price/qty on the order book for a symbol or symbols.' } ], default: 'getOrderBook' } ] as INodeParams[] this.inputParameters = [ { label: 'Symbol', name: 'symbol', type: 'asyncOptions', optional: { 'actions.operation': ['getExchangeInfo'] }, loadMethod: 'getSupportedSymbols', show: { 'actions.operation': [ 'getOrderBook', 'getRecentTradesList', 'getAggTrades', 'getKlines', 'getAvgPrice', 'get24hrTickerPrice', 'getTickerPrice', 'getBookTicker', 'getExchangeInfo' ] } }, { label: 'Limit', name: 'limit', type: 'number', default: 100, optional: true, show: { 'actions.operation': ['getOrderBook', 'getRecentTradesList', 'getAggTrades', 'getKlines'] } }, { label: 'From Id', name: 'fromId', type: 'number', optional: true, description: 'ID to get aggregate trades from INCLUSIVE.', show: { 'actions.operation': ['getAggTrades'] } }, { label: 'Start Time', name: 'startTime', type: 'date', optional: true, show: { 'actions.operation': ['getAggTrades', 'getKlines'] } }, { label: 'End Time', name: 'endTime', type: 'date', optional: true, show: { 'actions.operation': ['getAggTrades', 'getKlines'] } }, { label: 'Interval', name: 'interval', type: 'options', options: [ { label: '1 minute', name: '1m' }, { label: '3 minutes', name: '3m' }, { label: '5 minutes', name: '5m' }, { label: '15 minutes', name: '15m' }, { label: '30 minutes', name: '30m' }, { label: '1 hour', name: '1h' }, { label: '2 hours', name: '2h' }, { label: '4 hours', name: '4h' }, { label: '6 hours', name: '6h' }, { label: '8 hours', name: '8h' }, { label: '12 hours', name: '12h' }, { label: '1 day', name: '1d' }, { label: '3 day', name: '3d' }, { label: '1 week', name: '1w' }, { label: '1 month', name: '1M' } ], default: '5m', show: { 'actions.operation': ['getKlines'] } } ] as INodeParams[] } loadMethods = { async getSupportedSymbols(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const actionData = nodeData.actions let apiUrl = '' if (actionData !== undefined && (actionData.network as string) === 'test') { apiUrl = 'https://testnet.binance.vision/api' } else { apiUrl = 'https://api.binance.com/api' } const axiosConfig: AxiosRequestConfig = { method: 'GET', url: `${apiUrl}/v3/exchangeInfo`, headers: { 'Content-Type': 'application/json' } } const response = await axios(axiosConfig) const responseData = response.data for (const s of responseData['symbols']) { returnData.push({ label: s.symbol, name: s.symbol }) } return returnData } } async run(nodeData: INodeData): Promise { const actionData = nodeData.actions const inputParametersData = nodeData.inputParameters if (actionData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } const network = actionData.network as string const operation = actionData.operation as string const returnData: ICommonObject[] = [] let responseData: any // tslint:disable-line: no-any let apiUrl = '' if (network === 'test') { apiUrl = 'https://testnet.binance.vision/api' } else if (network === 'live') { apiUrl = 'https://api.binance.com/api' } let url = '' const queryParameters: ICommonObject = {} const queryBody: ICommonObject = {} let method: Method = 'GET' try { if (operation === 'getOrderBook') { const symbol = inputParametersData.symbol as string const limit = inputParametersData.limit as number url = `${apiUrl}/v3/depth` queryParameters['symbol'] = symbol queryParameters['limit'] = limit method = 'GET' } else if (operation === 'getExchangeInfo') { const symbol = inputParametersData.symbol as string if (symbol) queryParameters['symbol'] = symbol url = `${apiUrl}/v3/exchangeInfo` method = 'GET' } else if (operation === 'getRecentTradesList') { const symbol = inputParametersData.symbol as string const limit = inputParametersData.limit as number url = `${apiUrl}/v3/trades` queryParameters['symbol'] = symbol queryParameters['limit'] = limit method = 'GET' } else if (operation === 'getAggTrades') { const symbol = inputParametersData.symbol as string const limit = inputParametersData.limit as number const fromId = inputParametersData.fromId as number const startTime = Date.parse(inputParametersData.startTime as string) const endTime = Date.parse(inputParametersData.endTime as string) url = `${apiUrl}/v3/aggTrades` queryParameters['symbol'] = symbol queryParameters['limit'] = limit if (fromId) queryParameters['fromId'] = fromId if (startTime) queryParameters['startTime'] = startTime if (endTime) queryParameters['endTime'] = endTime method = 'GET' } else if (operation === 'getKlines') { const symbol = inputParametersData.symbol as string const limit = inputParametersData.limit as number const interval = inputParametersData.interval as string const startTime = Date.parse(inputParametersData.startTime as string) const endTime = Date.parse(inputParametersData.endTime as string) url = `${apiUrl}/v3/klines` queryParameters['symbol'] = symbol queryParameters['limit'] = limit queryParameters['interval'] = interval if (startTime) queryParameters['startTime'] = startTime if (endTime) queryParameters['endTime'] = endTime method = 'GET' } else if (operation === 'getAvgPrice') { const symbol = inputParametersData.symbol as string url = `${apiUrl}/v3/avgPrice` queryParameters['symbol'] = symbol method = 'GET' } else if (operation === 'get24hrTickerPrice') { const symbol = inputParametersData.symbol as string url = `${apiUrl}/v3/ticker/24hr` queryParameters['symbol'] = symbol method = 'GET' } else if (operation === 'getTickerPrice') { const symbol = inputParametersData.symbol as string url = `${apiUrl}/v3/ticker/price` queryParameters['symbol'] = symbol method = 'GET' } else if (operation === 'getBookTicker') { const symbol = inputParametersData.symbol as string url = `${apiUrl}/v3/ticker/bookTicker` queryParameters['symbol'] = symbol method = 'GET' } const axiosConfig: AxiosRequestConfig = { method, url, params: queryParameters, paramsSerializer: (params) => serializeQueryParams(params), headers: { 'Content-Type': 'application/json' } } if (Object.keys(queryBody).length > 0) { axiosConfig.data = queryBody } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) returnData.push(...responseData) else returnData.push(responseData) return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: BinancePublic } ================================================ FILE: packages/components/nodes/BlockchainEvent/BlockchainEvent.ts ================================================ import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams, IProviders, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import EventEmitter from 'events' import { ArbitrumNetworks, ETHNetworks, OptimismNetworks, PolygonNetworks, getNetworkProvidersList, NETWORK, getNetworkProvider, NETWORK_PROVIDER, networkProviderCredentials, BSCNetworks, AvalancheNetworks, SolanaNetworks, FantomNetworks, GnosisNetworks, HecoNetworks, HarmonyNetworks, MoonRiverNetworks, MoonBeamNetworks, MetisNetworks, KlatynNetworks } from '../../src/ChainNetwork' import { ethers } from 'ethers' class BlockchainEvent extends EventEmitter implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] networks?: INodeParams[] credentials?: INodeParams[] providers: IProviders constructor() { super() this.label = 'Blockchain Event Trigger' this.name = 'blockchainEventTrigger' this.icon = 'blockchainevent.svg' this.type = 'trigger' this.category = 'Blockchain Events' this.version = 1.0 this.description = 'Start workflow whenever a specified event happened on chain' this.incoming = 0 this.outgoing = 1 this.providers = {} this.actions = [ { label: 'Event Name', name: 'event', type: 'options', options: [ { label: 'New Block', name: 'block', description: 'Emitted when a new block is mined' }, { label: 'Error', name: 'error', description: 'emitted on any error' }, { label: 'New Transaction', name: 'pending', description: 'Emitted when a new transaction enters the memory pool. Only certain providers offer this event and may require running your own node for reliable results' }, { label: 'Transaction Hash', name: 'txHash', description: 'Emitted when the transaction has been mined' } ], default: 'block' }, { label: 'Transaction Hash', name: 'txHash', type: 'string', default: '', show: { 'actions.event': ['txHash'] } } ] this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [ ...ETHNetworks, ...PolygonNetworks, ...ArbitrumNetworks, ...OptimismNetworks, ...BSCNetworks, ...AvalancheNetworks, ...SolanaNetworks, ...FantomNetworks, ...GnosisNetworks, ...HecoNetworks, ...HarmonyNetworks, ...MoonRiverNetworks, ...MoonBeamNetworks, ...MetisNetworks, ...KlatynNetworks ], default: 'homestead' }, { label: 'Network Provider', name: 'networkProvider', type: 'asyncOptions', loadMethod: 'getNetworkProviders' }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] } loadMethods = { async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks const actionsData = nodeData.actions if (networksData === undefined || actionsData === undefined) return returnData const network = networksData.network as NETWORK if (actionsData.event === 'pending') { return [ { label: 'Custom Websocket Endpoint', name: NETWORK_PROVIDER.CUSTOMWSS, description: 'WSS Endpoint', parentGroup: 'Custom Nodes' } ] } return getNetworkProvidersList(network) } } async runTrigger(nodeData: INodeData): Promise { const networksData = nodeData.networks const credentials = nodeData.credentials const actionsData = nodeData.actions if (networksData === undefined || actionsData === undefined) { throw new Error('Required data missing') } const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string, true ) if (!provider) throw new Error('Invalid Network Provider') const emitEventKey = nodeData.emitEventKey as string let event = actionsData.event as string if (event === 'txHash') event = actionsData.txHash as string try { provider.on(event, async (result: any) => { const returnData = await getOutputResponse(event, result, provider) this.emit(emitEventKey, returnNodeExecutionData(returnData)) }) } catch (error) { throw handleErrorMessage(error) } this.providers[emitEventKey] = { provider, filter: event } } async removeTrigger(nodeData: INodeData): Promise { const emitEventKey = nodeData.emitEventKey as string if (Object.prototype.hasOwnProperty.call(this.providers, emitEventKey)) { const provider = this.providers[emitEventKey].provider const filter = this.providers[emitEventKey].filter provider.off(filter) this.removeAllListeners(emitEventKey) } } } const getOutputResponse = async ( event: string, result: any, provider: ethers.providers.JsonRpcProvider | ethers.providers.FallbackProvider | ethers.providers.WebSocketProvider ) => { let returnItem = {} as ICommonObject switch (event) { case 'block': returnItem = { blockNumber: result } break case 'error': returnItem = { error: result } break case 'pending': returnItem = { pendingTransactionHash: result, pendingTransaction: ((await provider.getTransaction(result)) as any) || {} } break case 'txHash': returnItem = { transaction: result } break } return returnItem } module.exports = { nodeClass: BlockchainEvent } ================================================ FILE: packages/components/nodes/BscScan/Bscscan.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' import { NETWORK, NETWORK_LABEL, etherscanAPIs } from '../../src/ChainNetwork' import axios, { AxiosRequestConfig, Method } from 'axios' import { SORT_BY, OPERATIONS, GET_BNB_BALANCE, GET_HISTORICAL_BNB_BALANCE, GET_NORMAL_TRANSACTIONS, GET_INTERNAL_TRANSACTIONS, GET_INTERNAL_TRANSACTIONS_BY_HASH, GET_INTERNAL_TRANSACTIONS_BY_BLOCK, GET_BLOCKS_VALIDATED, GET_ABI, GET_CONTRACT_SOURCE_CODE, CHECK_TRANSACTION_RECEIPT_STATUS, GET_BEP20_TOKEN_SUPPLY, GET_BEP20_TOKEN_BALANCE, GET_HISTORICAL_BEP20_TOKEN_SUPPLY, GET_HISTORICAL_BEP20_TOKEN_BALANCE, GET_TOKEN_INFO, GET_BNB_PRICE, GET_HISTORICAL_BNB_PRICE, GET_MULTI_BNB_BALANCE, GET_BEP20_CIRCULATION_TOKEN_SUPPLY } from './constants' class Bscscan implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions: INodeParams[] credentials?: INodeParams[] networks?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'BscScan' this.name = 'BscScan' this.icon = 'bscscan.svg' this.type = 'action' this.category = 'Block Explorer' this.version = 1.0 this.description = 'Perform Bscscan operations' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'API', name: 'api', type: 'options', options: [ { label: 'Get Bnb Balance for a Single Address', name: GET_BNB_BALANCE.name, description: 'Returns the Bnb balance of a given address.' }, { label: 'Get Bnb Balance for Multiple Addresses(separated by a comma)', name: GET_MULTI_BNB_BALANCE.name, description: 'Returns the Bnb balance of the addresses(each address separated by a comma) entered.' }, { label: 'Get transactions', name: GET_NORMAL_TRANSACTIONS.name, description: 'Returns the list of transactions performed by an address, with optional pagination.' }, { label: 'Get internal transactions', name: GET_INTERNAL_TRANSACTIONS.name, description: 'Returns the list of internal transactions performed by an address, with optional pagination.' }, { label: 'Get internal transactions by hash', name: GET_INTERNAL_TRANSACTIONS_BY_HASH.name, description: 'Returns the list of internal transactions performed within a transaction.' }, { label: 'Get internal transactions by block', name: GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name, description: 'Returns the list of internal transactions performed within a block range, with optional pagination.' }, { label: 'Get list of Blocks Validated by Address', name: GET_BLOCKS_VALIDATED.name, description: 'Returns the list of blocks validated by an address.' }, { label: 'Get Contract ABI', name: GET_ABI.name, description: 'Returns the contract Application Binary Interface ( ABI ) of a verified smart contract.' }, { label: 'Get Contract Source Code', name: GET_CONTRACT_SOURCE_CODE.name, description: 'Returns the Solidity source code of a verified smart contract.' }, { label: 'Check Transaction Receipt Status', name: CHECK_TRANSACTION_RECEIPT_STATUS.name, description: 'Returns the status code of a transaction execution.' }, { label: 'Get BEP-20 Token Circulating Supply by ContractAddress', name: GET_BEP20_CIRCULATION_TOKEN_SUPPLY.name, description: `Returns the current circulating supply of a BEP-20 token. Eg. a token with a balance of 215.241526476136819398 and 18 decimal places will be returned as 215241526476136819398` }, { label: 'Get BEP20 Token Supply', name: GET_BEP20_TOKEN_SUPPLY.name, description: `Returns the total supply of a BEP-20 token. The result is returned in the token's smallest decimal representation. Eg. a token with a balance of 215.241526476136819398 and 18 decimal places will be returned as 215241526476136819398` }, { label: 'Get BEP20 Token Balance', name: GET_BEP20_TOKEN_BALANCE.name, description: `Returns the current balance of a BEP-20 token of an address. The result is returned in the token's smallest decimal representation. Eg. a token with a balance of 215.241526476136819398 and 18 decimal places will be returned as 215241526476136819398` }, { label: 'Get Historical BEP-20 Token TotalSupply by ContractAddress & BlockNo [PRO]', name: GET_HISTORICAL_BEP20_TOKEN_SUPPLY.name, description: `Returns the historical amount of a BEP-20 token in circulation at a certain block height. The result is returned in the token's smallest decimal representation. Eg. a token with a balance of 215.241526476136819398 and 18 decimal places will be returned as 215241526476136819398` }, { label: 'Get Historical BEP-20 Token Account Balance by ContractAddress & BlockNo [PRO]', name: GET_HISTORICAL_BEP20_TOKEN_BALANCE.name, description: `Returns the balance of a BEP-20 token of an address at a certain block height. The result is returned in the token's smallest decimal representation. Eg. a token with a balance of 215.241526476136819398 and 18 decimal places will be returned as 215241526476136819398` }, { label: 'Get Token Info [PRO]', name: GET_TOKEN_INFO.name, description: 'Returns project information and social media links of an BEP-20/ERC-721 token.' }, { label: 'Get BNB Price', name: GET_BNB_PRICE.name, description: 'Returns the latest price of 1 BNB.' }, { label: 'Get Historical BNB Price [PRO]', name: GET_HISTORICAL_BNB_PRICE.name, description: 'Returns the historical price of BNB.' } ], default: 'getBnbBalance' } ] as INodeParams[] this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [ { label: NETWORK_LABEL.BSC, name: NETWORK.BSC }, { label: NETWORK_LABEL.BSC_TESTNET, name: NETWORK.BSC_TESTNET } ], default: 'homestead' } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'BscScan API Key', name: 'bscscanApi' } ], default: 'bscscanApi' } ] as INodeParams[] this.inputParameters = [ { label: 'Address', name: 'address', type: 'string', description: 'The address parameter(s) required', show: { 'actions.api': [ GET_BNB_BALANCE.name, GET_HISTORICAL_BNB_BALANCE.name, GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_BLOCKS_VALIDATED.name, GET_ABI.name, GET_CONTRACT_SOURCE_CODE.name, GET_BEP20_TOKEN_BALANCE.name, GET_HISTORICAL_BEP20_TOKEN_BALANCE.name ] } }, { label: 'Block Number', name: 'blockno', type: 'number', description: 'the block number to check balance for eg. 2000000', show: { 'actions.api': [ GET_HISTORICAL_BNB_BALANCE.name, GET_HISTORICAL_BEP20_TOKEN_SUPPLY.name, GET_HISTORICAL_BEP20_TOKEN_BALANCE.name ] } }, { label: 'Start Block', name: 'startBlock', type: 'number', optional: true, description: 'the block number to start searching for transactions', show: { 'actions.api': [GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name] }, default: 0 }, { label: 'End Block', name: 'endBlock', type: 'number', optional: true, description: 'the block number to stop searching for transactions', show: { 'actions.api': [GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name] } }, { label: 'Page', name: 'page', type: 'number', optional: true, description: 'the page number, if pagination is enabled', show: { 'actions.api': [ GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name, GET_BLOCKS_VALIDATED.name ] }, default: 1 }, { label: 'Offset', name: 'offset', type: 'number', optional: true, description: 'the number of transactions displayed per page', show: { 'actions.api': [ GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name, GET_BLOCKS_VALIDATED.name ] }, default: 10 }, { label: 'Sort By', name: 'sortBy', type: 'options', optional: true, options: SORT_BY, show: { 'actions.api': [GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name] }, default: 'desc' }, { label: 'Transaction Hash', name: 'txhash', type: 'string', description: 'the string representing the transaction hash to check for internal transactions', show: { 'actions.api': [GET_INTERNAL_TRANSACTIONS_BY_HASH.name, CHECK_TRANSACTION_RECEIPT_STATUS.name] } }, { label: 'Block Type', name: 'blockType', type: 'options', options: [ { label: 'blocks', name: 'blocks' } ], default: 'blocks', show: { 'actions.api': [GET_BLOCKS_VALIDATED.name] } }, { label: 'Contract Address', name: 'contractAddress', type: 'string', description: 'the contract address of the BEP-20 token', show: { 'actions.api': [ GET_BEP20_TOKEN_SUPPLY.name, GET_BEP20_TOKEN_BALANCE.name, GET_HISTORICAL_BEP20_TOKEN_SUPPLY.name, GET_HISTORICAL_BEP20_TOKEN_BALANCE.name, GET_TOKEN_INFO.name, GET_BEP20_CIRCULATION_TOKEN_SUPPLY.name ] } }, { label: 'Tag', name: 'tag', type: 'options', options: [{ label: 'latest', name: 'latest' }], default: 'latest', show: { 'actions.api': [GET_BEP20_TOKEN_BALANCE.name] } }, { label: 'Start Time', name: 'startTime', type: 'date', optional: true, show: { 'actions.api': [GET_HISTORICAL_BNB_PRICE.name] } }, { label: 'End Time', name: 'endTime', type: 'date', optional: true, show: { 'actions.api': [GET_HISTORICAL_BNB_PRICE.name] } } ] as INodeParams[] } getNetwork(network: NETWORK): string { return `${etherscanAPIs[network]}` } getBaseParams(api: string) { const operation = OPERATIONS.filter(({ name }) => name === api)[0] return { module: operation.module, action: operation.action } } getISODate(date: Date) { return date.toISOString().split('T')[0] } async run(nodeData: INodeData): Promise { const actionData = nodeData.actions const networksData = nodeData.networks const inputParameters = nodeData.inputParameters const credentials = nodeData.credentials if (actionData === undefined || inputParameters === undefined || credentials === undefined || networksData === undefined) { throw new Error('Required data missing') } // GET api const api = actionData.api as string // GET network const network = networksData.network as NETWORK // GET credentials const apiKey = credentials.apiKey as string // GET address const address = inputParameters.address as string const startblock = inputParameters.startBlock as number const endblock = inputParameters.endBlock as number const page = inputParameters.page as number const offset = inputParameters.offset as number const sort = inputParameters.sortBy as string const txhash = inputParameters.txhash as string const blocktype = inputParameters.blockType as string const contractaddress = inputParameters.contractAddress as string const tag = inputParameters.tag as string const startTime = inputParameters.startTime as string const endTime = inputParameters.endTime as string const startdate = startTime ? this.getISODate(new Date(startTime)) : undefined const enddate = endTime ? this.getISODate(new Date(endTime)) : undefined const url = this.getNetwork(network) const { module, action } = this.getBaseParams(api) const queryParameters = { module, action, address, apiKey, startblock, endblock, page, offset, sort, txhash, blocktype, contractaddress, tag, startdate, enddate } const returnData: ICommonObject[] = [] let responseData: any try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url, params: queryParameters, paramsSerializer: (params) => serializeQueryParams(params), headers: { 'Content-Type': 'application/json' } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: Bscscan } ================================================ FILE: packages/components/nodes/BscScan/constants.ts ================================================ // Account export const GET_BNB_BALANCE = { name: 'getBnbBalance', module: 'account', action: 'balance' } export const GET_MULTI_BNB_BALANCE = { name: 'getBnbBalanceMulti', module: 'account', action: 'balancemulti' } export const GET_HISTORICAL_BNB_BALANCE = { name: 'getHistoricalBnbBalance', module: 'account', action: 'balancehistory' } export const GET_NORMAL_TRANSACTIONS = { name: 'getTransactions', module: 'account', action: 'txlist' } export const GET_INTERNAL_TRANSACTIONS = { name: 'getInternalTransactions', module: 'account', action: 'txlistinternal' } export const GET_INTERNAL_TRANSACTIONS_BY_HASH = { name: 'getInternalTransactionsByHash', module: 'account', action: 'txlistinternal' } export const GET_INTERNAL_TRANSACTIONS_BY_BLOCK = { name: 'getInternalTransactionsByBlock', module: 'account', action: 'txlistinternal' } export const GET_BLOCKS_VALIDATED = { name: 'getBlocksValidated', module: 'account', action: 'getminedblocks' } // Contracts export const GET_ABI = { name: 'getAbi', module: 'contract', action: 'getabi' } export const GET_CONTRACT_SOURCE_CODE = { name: 'getContractSourceCode', module: 'contract', action: 'getsourcecode' } export const GET_CONTRACT_CREATION = { name: 'getContractCreation', module: 'contract', action: 'getcontractcreation' } // Transactions export const CHECK_TRANSACTION_RECEIPT_STATUS = { name: 'getTransactionReceiptStatus', module: 'transaction', action: 'gettxreceiptstatus' } // Tokens export const GET_BEP20_CIRCULATION_TOKEN_SUPPLY = { name: 'getBep20TokenCirculatingSupply', module: 'stats', action: 'tokenCsupply' } export const GET_BEP20_TOKEN_SUPPLY = { name: 'getBep20TokenSupply', module: 'stats', action: 'tokensupply' } export const GET_BEP20_TOKEN_BALANCE = { name: 'getBep20TokenBalance', module: 'account', action: 'tokenbalance' } export const GET_HISTORICAL_BEP20_TOKEN_SUPPLY = { name: 'getHistoricalBep20TokenSupply', module: 'stats', action: 'tokensupplyhistory' } export const GET_HISTORICAL_BEP20_TOKEN_BALANCE = { name: 'getHistoricalBep20TokenBalance', module: 'account', action: 'tokenbalancehistory' } export const GET_TOKEN_INFO = { name: 'getTokenInfo', module: 'token', action: 'tokeninfo' } // Stats export const GET_BNB_SUPPLY = { name: 'getBep20TokenSupply', module: 'stats', action: 'tokensupply' } export const GET_BNB_PRICE = { name: 'getBnbPrice', module: 'stats', action: 'bnbprice' } export const GET_HISTORICAL_BNB_PRICE = { name: 'getHistoricalBnbPrice', module: 'stats', action: 'bnbdailyprice' } export const OPERATIONS = [ GET_BNB_BALANCE, GET_MULTI_BNB_BALANCE, GET_HISTORICAL_BNB_BALANCE, GET_NORMAL_TRANSACTIONS, GET_INTERNAL_TRANSACTIONS, GET_INTERNAL_TRANSACTIONS_BY_HASH, GET_INTERNAL_TRANSACTIONS_BY_BLOCK, GET_BLOCKS_VALIDATED, GET_ABI, GET_CONTRACT_SOURCE_CODE, GET_CONTRACT_CREATION, CHECK_TRANSACTION_RECEIPT_STATUS, GET_BEP20_TOKEN_SUPPLY, GET_BEP20_TOKEN_BALANCE, GET_HISTORICAL_BEP20_TOKEN_SUPPLY, GET_HISTORICAL_BEP20_TOKEN_BALANCE, GET_TOKEN_INFO, GET_BNB_PRICE, GET_HISTORICAL_BNB_PRICE, GET_BNB_SUPPLY, GET_BEP20_CIRCULATION_TOKEN_SUPPLY ] as const export const SORT_BY = [ { label: 'Desc', name: 'desc' }, { label: 'Asc', name: 'asc' } ] ================================================ FILE: packages/components/nodes/ChainLink/ChainLink.ts ================================================ import { ethers } from 'ethers' import { ICommonObject, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import { getNetworkProvidersList, NETWORK_PROVIDER, getNetworkProvider, NETWORK, networkProviderCredentials } from '../../src/ChainNetwork' import axios, { AxiosRequestConfig, Method } from 'axios' import { chainLinkNetworks, chainLinkNetworkMapping } from './supportedNetwork' class ChainLink implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] actions?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'ChainLink' this.name = 'chainLink' this.icon = 'chainlink.svg' this.type = 'action' this.category = 'Decentralized Oracle Network' this.version = 1.0 this.description = 'Execute ChainLink operations such as Data Feeds, Randomness, Oracles.' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Operation', name: 'operation', type: 'options', options: [ { label: 'Get Price Feeds', name: 'getPriceFeeds', description: 'Get real-world market prices of assets using ChainLink Oracle' }, { label: 'Get Proof of Reserve', name: 'getProofReserve', description: 'Provide the status of the reserves for several assets' }, { label: 'Get NFT Floor Pricing', name: 'getNFTFloorPricing', description: 'Get the lowest price of an NFT in a collection using ChainLink Oracle, ONLY available on Goerli' } ], default: 'getPriceFeeds' } ] this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...chainLinkNetworks] }, { label: 'Network Provider', name: 'networkProvider', type: 'asyncOptions', loadMethod: 'getNetworkProviders' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] this.inputParameters = [ { label: 'Pair', name: 'pair', type: 'asyncOptions', loadMethod: 'getPairAddress', show: { 'actions.operation': ['getPriceFeeds'] } }, { label: 'Reserve', name: 'reserve', type: 'asyncOptions', loadMethod: 'getReserveAddress', show: { 'actions.operation': ['getProofReserve'] } }, { label: 'NFT Collection', name: 'nftCollection', type: 'asyncOptions', loadMethod: 'getNftCollection', show: { 'actions.operation': ['getNFTFloorPricing'] } } ] as INodeParams[] } loadMethods = { async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK return getNetworkProvidersList(network) }, async getPairAddress(nodeData: INodeData): Promise { const networksData = nodeData.networks if (networksData === undefined) return [] const network = networksData.network as NETWORK return await getInputParametersData(network, 'default') }, async getNftCollection(nodeData: INodeData): Promise { const networksData = nodeData.networks if (networksData === undefined) return [] const network = networksData.network as NETWORK return await getInputParametersData(network, 'nftFloor') }, async getReserveAddress(nodeData: INodeData): Promise { const networksData = nodeData.networks if (networksData === undefined) return [] const network = networksData.network as NETWORK return await getInputParametersData(network, 'por') } } async run(nodeData: INodeData): Promise { const networksData = nodeData.networks const actionsData = nodeData.actions const inputParametersData = nodeData.inputParameters const credentials = nodeData.credentials if (networksData === undefined || actionsData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } try { const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) { throw new Error('Invalid Network Provider') } const operation = actionsData.operation as string if (operation === 'getPriceFeeds' || operation === 'getProofReserve' || operation === 'getNFTFloorPricing') { const pair = inputParametersData.pair as string const parsedPair = JSON.parse(pair.replace(/\s/g, '')) const address = parsedPair.proxy const aggregatorV3InterfaceABI = [ { inputs: [], name: 'decimals', outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }], stateMutability: 'view', type: 'function' }, { inputs: [], name: 'description', outputs: [{ internalType: 'string', name: '', type: 'string' }], stateMutability: 'view', type: 'function' }, { inputs: [{ internalType: 'uint80', name: '_roundId', type: 'uint80' }], name: 'getRoundData', outputs: [ { internalType: 'uint80', name: 'roundId', type: 'uint80' }, { internalType: 'int256', name: 'answer', type: 'int256' }, { internalType: 'uint256', name: 'startedAt', type: 'uint256' }, { internalType: 'uint256', name: 'updatedAt', type: 'uint256' }, { internalType: 'uint80', name: 'answeredInRound', type: 'uint80' } ], stateMutability: 'view', type: 'function' }, { inputs: [], name: 'latestRoundData', outputs: [ { internalType: 'uint80', name: 'roundId', type: 'uint80' }, { internalType: 'int256', name: 'answer', type: 'int256' }, { internalType: 'uint256', name: 'startedAt', type: 'uint256' }, { internalType: 'uint256', name: 'updatedAt', type: 'uint256' }, { internalType: 'uint80', name: 'answeredInRound', type: 'uint80' } ], stateMutability: 'view', type: 'function' }, { inputs: [], name: 'version', outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], stateMutability: 'view', type: 'function' } ] const priceFeed = new ethers.Contract(address, aggregatorV3InterfaceABI, provider) const roundData = await priceFeed.latestRoundData() const returnItem: ICommonObject = { roundData, ...parsedPair, network } return returnNodeExecutionData(returnItem) } return returnNodeExecutionData([]) } catch (e) { throw handleErrorMessage(e) } } } const getInputParametersData = async (network: NETWORK, dataType: string) => { const returnData: INodeOptionsValue[] = [] try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://cl-docs-addresses.web.app/addresses.json` } const response = await axios(axiosConfig) const responseData = response.data for (const parentNetwork in responseData) { const availableNetworks = responseData[parentNetwork].networks const selectedNetwork = availableNetworks.find( (ntk: any) => ntk.name === chainLinkNetworkMapping[network] && ntk.dataType === dataType ) let availableProxies = [] if (selectedNetwork) { availableProxies = selectedNetwork.proxies } for (const proxy of availableProxies) { const data = { label: proxy.pair, name: JSON.stringify(proxy) } as INodeOptionsValue returnData.push(data) } } return returnData } catch (e) { return returnData } } module.exports = { nodeClass: ChainLink } ================================================ FILE: packages/components/nodes/ChainLink/ChainLinkFunctionWebhook.ts ================================================ import { ICommonObject, IDbCollection, INode, INodeData, INodeOptionsValue, INodeParams, IWebhookNodeExecutionData, NodeType } from '../../src/Interface' import { compareKeys, returnWebhookNodeExecutionData } from '../../src/utils' class ChainLinkFunctionWebhook implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number inputParameters?: INodeParams[] constructor() { this.label = 'Chainlink Function Webhook' this.icon = 'chainlink.svg' this.name = 'chainLinkFunctionWebhook' this.type = 'webhook' this.category = 'Decentralized Oracle Network' this.version = 1.0 this.description = 'Start workflow whenever chainlink function webhook is called' this.incoming = 0 this.outgoing = 1 this.inputParameters = [ { label: 'HTTP Method', name: 'httpMethod', type: 'options', options: [ { label: 'GET', name: 'GET' }, { label: 'POST', name: 'POST' } ], default: 'GET', description: 'The HTTP method to listen to.' }, { label: 'Authorization', name: 'authorization', type: 'options', options: [ { label: 'API', name: 'headerAuth', description: 'Webhook header must contains "X-API-KEY" with matching key' }, { label: 'None', name: 'none' } ], default: 'none', description: 'The way to authorize incoming webhook.' }, { label: 'API key', name: 'apiKey', type: 'asyncOptions', description: 'Incoming call must consists header "x-api-key" with matching API key. You can create new key from the dashboard', loadMethod: 'getAPIKeys', show: { 'inputParameters.authorization': ['headerAuth'] } } ] } loadMethods = { async getAPIKeys(nodeData: INodeData, dbCollection?: IDbCollection, apiKeys?: ICommonObject[]): Promise { const returnData: INodeOptionsValue[] = [] if (!apiKeys || !apiKeys.length) return returnData for (let i = 0; i < apiKeys.length; i += 1) { const key = apiKeys[i] const data = { label: key.keyName, description: key.apiKey, name: key.apiSecret } as INodeOptionsValue returnData.push(data) } return returnData } } async runWebhook(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters const req = nodeData.req if (inputParametersData === undefined) { throw new Error('Required data missing') } if (req === undefined) { throw new Error('Missing request') } const authorization = inputParametersData.authorization as string const apiSecret = inputParametersData.apiKey as string const returnData: ICommonObject[] = [] if (authorization === 'headerAuth') { let suppliedKey = '' if (req.headers['X-API-KEY']) suppliedKey = req.headers['X-API-KEY'] as string if (req.headers['x-api-key']) suppliedKey = req.headers['x-api-key'] as string if (!suppliedKey) throw new Error('401: Missing API Key') const isKeyValid = compareKeys(apiSecret, suppliedKey) if (!isKeyValid) throw new Error('403: Unauthorized API Key') } returnData.push({ headers: req?.headers, params: req?.params, query: req?.query, body: req?.body, rawBody: (req as any).rawBody, url: req?.url }) return returnWebhookNodeExecutionData(returnData) } } module.exports = { nodeClass: ChainLinkFunctionWebhook } ================================================ FILE: packages/components/nodes/ChainLink/supportedNetwork.ts ================================================ import { INodeOptionsValue } from '../../src' import { NETWORK, NETWORK_LABEL } from '../../src/ChainNetwork' export type IChainLinkNetworkMapping = { [key in NETWORK]: string } export const chainLinkNetworkMapping = { [NETWORK.MAINNET]: 'Ethereum Mainnet', [NETWORK.GÖRLI]: 'Goerli Testnet', [NETWORK.ARBITRUM]: 'Arbitrum Mainnet', [NETWORK.ARBITRUM_GOERLI]: 'Arbitrum Goerli', [NETWORK.OPTIMISM]: 'Optimism Mainnet', [NETWORK.OPTIMISM_GOERLI]: 'Optimism Goerli', [NETWORK.MATIC]: 'Polygon Mainnet', [NETWORK.MATIC_MUMBAI]: 'Mumbai Testnet', [NETWORK.BSC]: 'BNB Chain Mainnet', [NETWORK.BSC_TESTNET]: 'BNB Chain Mainnet', [NETWORK.GNOSIS]: 'Gnosis Chain Mainnet', [NETWORK.HECO]: 'HECO Mainnet', [NETWORK.FANTOM]: 'Fantom Mainnet', [NETWORK.FANTOM_TESTNET]: 'Fantom Testnet', [NETWORK.AVALANCHE]: 'Avalanche Mainnet', [NETWORK.AVALANCHE_TESTNET]: 'Avalanche Testnet', [NETWORK.SOLANA]: 'Solana Mainnet', [NETWORK.SOLANA_DEVNET]: 'Solana Devnet', [NETWORK.HARMONY]: 'Harmony Mainnet', [NETWORK.MOONRIVER]: 'Moonriver Mainnet', [NETWORK.MOONBEAM]: 'Moonbeam Mainnet', [NETWORK.METIS]: 'Metis Mainnet', [NETWORK.KLATYN_TESTNET]: 'Klaytn Baobab testnet' } as IChainLinkNetworkMapping export const chainLinkNetworks: INodeOptionsValue[] = [ { label: NETWORK_LABEL.MAINNET, name: NETWORK.MAINNET, parentGroup: 'Ethereum', hide: { 'actions.operation': ['getNFTFloorPricing'] } }, { label: NETWORK_LABEL.GÖRLI, name: NETWORK.GÖRLI, parentGroup: 'Ethereum', hide: { 'actions.operation': ['getProofReserve'] } }, { label: NETWORK_LABEL.MATIC, name: NETWORK.MATIC, parentGroup: 'Polygon', hide: { 'actions.operation': ['getProofReserve', 'getNFTFloorPricing'] } }, { label: NETWORK_LABEL.MATIC_MUMBAI, name: NETWORK.MATIC_MUMBAI, parentGroup: 'Polygon', hide: { 'actions.operation': ['getProofReserve', 'getNFTFloorPricing'] } }, { label: NETWORK_LABEL.ARBITRUM, name: NETWORK.ARBITRUM, parentGroup: 'Arbitrum', hide: { 'actions.operation': ['getProofReserve', 'getNFTFloorPricing'] } }, { label: NETWORK_LABEL.ARBITRUM_GOERLI, name: NETWORK.ARBITRUM_GOERLI, parentGroup: 'Arbitrum', hide: { 'actions.operation': ['getProofReserve', 'getNFTFloorPricing'] } }, { label: NETWORK_LABEL.AVALANCHE, name: NETWORK.AVALANCHE, parentGroup: 'Avalanche', hide: { 'actions.operation': ['getNFTFloorPricing'] } }, { label: NETWORK_LABEL.AVALANCHE_TESTNET, name: NETWORK.AVALANCHE_TESTNET, parentGroup: 'Avalanche', hide: { 'actions.operation': ['getNFTFloorPricing'] } }, { label: NETWORK_LABEL.OPTIMISM, name: NETWORK.OPTIMISM, parentGroup: 'Optimism', hide: { 'actions.operation': ['getProofReserve', 'getNFTFloorPricing'] } }, { label: NETWORK_LABEL.OPTIMISM_GOERLI, name: NETWORK.OPTIMISM_GOERLI, parentGroup: 'Optimism', hide: { 'actions.operation': ['getProofReserve', 'getNFTFloorPricing'] } }, { label: NETWORK_LABEL.BSC, name: NETWORK.BSC, parentGroup: 'Binance Smart Chain', hide: { 'actions.operation': ['getNFTFloorPricing'] } }, { label: NETWORK_LABEL.BSC_TESTNET, name: NETWORK.BSC_TESTNET, parentGroup: 'Binance Smart Chain', hide: { 'actions.operation': ['getProofReserve', 'getNFTFloorPricing'] } }, { label: NETWORK_LABEL.GNOSIS, name: NETWORK.GNOSIS, parentGroup: 'Gnosis', hide: { 'actions.operation': ['getProofReserve', 'getNFTFloorPricing'] } }, { label: NETWORK_LABEL.HECO, name: NETWORK.HECO, parentGroup: 'Heco', hide: { 'actions.operation': ['getProofReserve', 'getNFTFloorPricing'] } }, { label: NETWORK_LABEL.FANTOM, name: NETWORK.FANTOM, parentGroup: 'Fantom', hide: { 'actions.operation': ['getProofReserve', 'getNFTFloorPricing'] } }, { label: NETWORK_LABEL.FANTOM_TESTNET, name: NETWORK.FANTOM_TESTNET, parentGroup: 'Fantom', hide: { 'actions.operation': ['getProofReserve', 'getNFTFloorPricing'] } }, { label: NETWORK_LABEL.SOLANA, name: NETWORK.SOLANA, parentGroup: 'Solana', hide: { 'actions.operation': ['getProofReserve', 'getNFTFloorPricing'] } }, { label: NETWORK_LABEL.SOLANA_DEVNET, name: NETWORK.SOLANA_DEVNET, parentGroup: 'Solana', hide: { 'actions.operation': ['getProofReserve', 'getNFTFloorPricing'] } }, { label: NETWORK_LABEL.HARMONY, name: NETWORK.HARMONY, parentGroup: 'Harmony', hide: { 'actions.operation': ['getProofReserve', 'getNFTFloorPricing'] } }, { label: NETWORK_LABEL.MOONRIVER, name: NETWORK.MOONRIVER, parentGroup: 'Moonriver', hide: { 'actions.operation': ['getProofReserve', 'getNFTFloorPricing'] } }, { label: NETWORK_LABEL.MOONBEAM, name: NETWORK.MOONBEAM, parentGroup: 'Moonbeam', hide: { 'actions.operation': ['getProofReserve', 'getNFTFloorPricing'] } }, { label: NETWORK_LABEL.METIS, name: NETWORK.METIS, parentGroup: 'Metis', hide: { 'actions.operation': ['getProofReserve', 'getNFTFloorPricing'] } }, { label: NETWORK_LABEL.KLATYN_TESTNET, name: NETWORK.KLATYN_TESTNET, parentGroup: 'Klaytn Baobab testnet', hide: { 'actions.operation': ['getProofReserve', 'getNFTFloorPricing'] } } ] ================================================ FILE: packages/components/nodes/ContractEventTrigger/ContractEventTrigger.ts ================================================ import { ethers, utils } from 'ethers' import { IContract, IDbCollection, INode, INodeData, INodeOptionsValue, INodeParams, IProviders, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import EventEmitter from 'events' import { networkExplorers, getNetworkProvidersList, NETWORK, NETWORK_PROVIDER, getNetworkProvider, networkProviderCredentials } from '../../src/ChainNetwork' class ContractEventTrigger extends EventEmitter implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] actions?: INodeParams[] providers: IProviders constructor() { super() this.label = 'Contract Event Trigger' this.name = 'ContractEventTrigger' this.icon = 'contract-event-trigger.svg' this.type = 'trigger' this.category = 'Smart Contract' this.version = 1.0 this.description = 'Start workflow whenever the specified contract event happened' this.incoming = 0 this.outgoing = 1 this.providers = {} this.actions = [ { label: 'Select Contract', name: 'contract', type: 'asyncOptions', loadFromDbCollections: ['Contract'], loadMethod: 'getContracts' }, { label: 'Event', name: 'event', type: 'asyncOptions', loadMethod: 'getEvents' } ] as INodeParams[] this.networks = [ { label: 'Network Provider', name: 'networkProvider', type: 'asyncOptions', loadMethod: 'getNetworkProviders' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] } loadMethods = { async getContracts(nodeData: INodeData, dbCollection?: IDbCollection): Promise { const returnData: INodeOptionsValue[] = [] if (dbCollection === undefined || !dbCollection || !dbCollection.Contract) { return returnData } const contracts: IContract[] = dbCollection.Contract for (let i = 0; i < contracts.length; i += 1) { const contract = contracts[i] const data = { label: `${contract.name} (${contract.network})`, name: JSON.stringify(contract), description: contract.address } as INodeOptionsValue returnData.push(data) } return returnData }, async getEvents(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const actionsData = nodeData.actions if (actionsData === undefined) { return returnData } const contractString = (actionsData.contract as string) || '' if (!contractString) return returnData try { const contractDetails = JSON.parse(contractString) if (!contractDetails.abi || !contractDetails.address) return returnData const abiString = contractDetails.abi const abi = JSON.parse(abiString) for (const item of abi) { if (!item.name) continue if (item.type === 'event') { const eventName = item.name const eventInputs = item.inputs let inputTypes = '' let value = '' for (let i = 0; i < eventInputs.length; i++) { const input = eventInputs[i] value += input.type inputTypes += `${input.type} ${input.name}` if (i !== eventInputs.length - 1) { inputTypes += ', ' value += ',' } } returnData.push({ label: eventName, name: `${eventName}(${value})`, description: inputTypes }) } } return returnData } catch (e) { return returnData } }, async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const actionData = nodeData.actions if (actionData === undefined) { return returnData } const contractString = (actionData.contract as string) || '' if (!contractString) return returnData try { const contractDetails = JSON.parse(contractString) if (!contractDetails.network) return returnData const network = contractDetails.network return getNetworkProvidersList(network) } catch (e) { return returnData } } } async runTrigger(nodeData: INodeData): Promise { const networksData = nodeData.networks const actionsData = nodeData.actions const credentials = nodeData.credentials if (networksData === undefined || actionsData === undefined) { throw new Error('Required data missing') } try { const contractString = (actionsData.contract as string) || '' const contractDetails: IContract = JSON.parse(contractString) const network = contractDetails.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') const address = contractDetails.address let abi = contractDetails.abi abi = JSON.parse(abi) const event = (actionsData.event as string) || '' const emitEventKey = nodeData.emitEventKey as string const filter = { address, topics: [utils.id(event)] } provider.on(filter, async (log: any) => { const txHash = log.transactionHash const iface = new ethers.utils.Interface(abi) const logs = await provider.getLogs(filter) const events = logs.map((log) => iface.parseLog(log)) log['logs'] = events log['explorerLink'] = `${networkExplorers[network]}/tx/${txHash}` this.emit(emitEventKey, returnNodeExecutionData(log)) }) this.providers[emitEventKey] = { provider, filter } } catch (e) { throw handleErrorMessage(e) } } async removeTrigger(nodeData: INodeData): Promise { const emitEventKey = nodeData.emitEventKey as string if (Object.prototype.hasOwnProperty.call(this.providers, emitEventKey)) { const provider = this.providers[emitEventKey].provider const filter = this.providers[emitEventKey].filter provider.off(filter) this.removeAllListeners(emitEventKey) } } } module.exports = { nodeClass: ContractEventTrigger } ================================================ FILE: packages/components/nodes/ContractFunctionTrigger/ContractFunctionTrigger.ts ================================================ import { CronJob } from 'cron' import { ethers } from 'ethers' import { IContract, ICronJobs, IDbCollection, INode, INodeData, INodeOptionsValue, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import EventEmitter from 'events' import { getNetworkProvidersList, NETWORK, NETWORK_PROVIDER, getNetworkProvider, networkProviderCredentials } from '../../src/ChainNetwork' class ContractFunctionTrigger extends EventEmitter implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] actions?: INodeParams[] cronJobs: ICronJobs constructor() { super() this.label = 'Contract Function Trigger' this.name = 'ContractFunctionTrigger' this.icon = 'contract-function-trigger.svg' this.type = 'trigger' this.category = 'Smart Contract' this.version = 1.0 this.description = 'Triggers whenever the specified view function return value changes' this.incoming = 0 this.outgoing = 1 this.cronJobs = {} this.actions = [ { label: 'Select Contract', name: 'contract', type: 'asyncOptions', loadFromDbCollections: ['Contract'], loadMethod: 'getContracts' }, { label: 'View Function', name: 'function', type: 'asyncOptions', loadMethod: 'getViewFunctions' }, { label: 'Function Parameters', name: 'funcParameters', type: 'json', placeholder: '["param1", "param2"]', description: 'Function parameters in array. Ex: ["param1", "param2"]', optional: true }, { label: 'Polling Time', name: 'pollTime', type: 'options', options: [ { label: 'Every 15 secs', name: '15s' }, { label: 'Every 30 secs', name: '30s' }, { label: 'Every 1 min', name: '1min' }, { label: 'Every 5 min', name: '5min' }, { label: 'Every 10 min', name: '10min' } ], default: '30s' } ] as INodeParams[] this.networks = [ { label: 'Network Provider', name: 'networkProvider', type: 'asyncOptions', loadMethod: 'getNetworkProviders' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] } loadMethods = { async getContracts(nodeData: INodeData, dbCollection?: IDbCollection): Promise { const returnData: INodeOptionsValue[] = [] if (dbCollection === undefined || !dbCollection || !dbCollection.Contract) { return returnData } const contracts: IContract[] = dbCollection.Contract for (let i = 0; i < contracts.length; i += 1) { const contract = contracts[i] const data = { label: `${contract.name} (${contract.network})`, name: JSON.stringify(contract), description: contract.address } as INodeOptionsValue returnData.push(data) } return returnData }, async getViewFunctions(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const actionsData = nodeData.actions if (actionsData === undefined) { return returnData } const contractString = (actionsData.contract as string) || '' if (!contractString) return returnData try { const contractDetails = JSON.parse(contractString) if (!contractDetails.abi || !contractDetails.address) return returnData const abiString = contractDetails.abi const abi = JSON.parse(abiString) for (const item of abi) { if (!item.name) continue if (item.type === 'function' && item.stateMutability === 'view') { const funcName = item.name const funcInputs = item.inputs let inputParameters = '' let inputTypes = '' for (let i = 0; i < funcInputs.length; i++) { const input = funcInputs[i] inputTypes += `${input.type} ${input.name}` if (i !== funcInputs.length - 1) inputTypes += ', ' inputParameters += `
  • ${input.type} ${input.name}
  • ` } if (inputParameters) { inputParameters = '
      ' + inputParameters + '
    ' } else { inputParameters = '
      ' + 'none' + '
    ' } returnData.push({ label: funcName, name: funcName, description: inputTypes, inputParameters }) } } return returnData } catch (e) { return returnData } }, async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const actionData = nodeData.actions if (actionData === undefined) { return returnData } const contractString = (actionData.contract as string) || '' if (!contractString) return returnData try { const contractDetails = JSON.parse(contractString) if (!contractDetails.network) return returnData const network = contractDetails.network return getNetworkProvidersList(network) } catch (e) { return returnData } } } async runTrigger(nodeData: INodeData): Promise { const networksData = nodeData.networks const actionsData = nodeData.actions const credentials = nodeData.credentials if (networksData === undefined || actionsData === undefined) { throw new Error('Required data missing') } try { const contractString = (actionsData.contract as string) || '' const contractDetails: IContract = JSON.parse(contractString) const network = contractDetails.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') const abiString = contractDetails.abi const address = contractDetails.address const abi = JSON.parse(abiString) const emitEventKey = nodeData.emitEventKey as string const functionName = (actionsData.function as string) || '' let contractParameters: any[] = [] const funcParameters = actionsData.funcParameters as string if (funcParameters) { try { contractParameters = JSON.parse(funcParameters.replace(/\s/g, '')) } catch (error) { throw handleErrorMessage(error) } } const contract = new ethers.Contract(address, abi, provider) const pollTime = (actionsData.pollTime as string) || '30s' const cronTimes: string[] = [] if (pollTime === '15s') { cronTimes.push(`*/15 * * * * *`) } else if (pollTime === '30s') { cronTimes.push(`*/30 * * * * *`) } else if (pollTime === '1min') { cronTimes.push(`*/1 * * * *`) } else if (pollTime === '5min') { cronTimes.push(`*/5 * * * *`) } else if (pollTime === '10min') { cronTimes.push(`*/10 * * * *`) } const lastResult = await contract[functionName].apply(null, contractParameters.length > 1 ? contractParameters : null) const executeTrigger = async () => { const newResult = await contract[functionName].apply(null, contractParameters.length > 1 ? contractParameters : null) if (JSON.stringify(newResult) !== JSON.stringify(lastResult)) { const returnItem = { function: functionName, oldValue: lastResult, newValue: newResult } this.emit(emitEventKey, returnNodeExecutionData(returnItem)) } } /// Start the cron-jobs if (Object.prototype.hasOwnProperty.call(this.cronJobs, emitEventKey)) { for (const cronTime of cronTimes) { // Automatically start the cron job this.cronJobs[emitEventKey].push(new CronJob(cronTime, executeTrigger, undefined, true)) } } else { for (const cronTime of cronTimes) { // Automatically start the cron job this.cronJobs[emitEventKey] = [new CronJob(cronTime, executeTrigger, undefined, true)] } } } catch (e) { throw handleErrorMessage(e) } } async removeTrigger(nodeData: INodeData): Promise { const emitEventKey = nodeData.emitEventKey as string if (Object.prototype.hasOwnProperty.call(this.cronJobs, emitEventKey)) { const cronJobs = this.cronJobs[emitEventKey] for (const cronJob of cronJobs) { cronJob.stop() } this.removeAllListeners(emitEventKey) } } } module.exports = { nodeClass: ContractFunctionTrigger } ================================================ FILE: packages/components/nodes/CreateERC20Token/CreateERC20Token.ts ================================================ import { ICommonObject, IDbCollection, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, IWallet, NodeType } from '../../src/Interface' import { getNodeModulesPackagePath, handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import { ethers } from 'ethers' import * as fs from 'fs' import { ArbitrumNetworks, BSCNetworks, ETHNetworks, getNetworkProvider, getNetworkProvidersList, NETWORK, networkExplorers, networkProviderCredentials, NETWORK_PROVIDER, OptimismNetworks, PolygonNetworks } from '../../src/ChainNetwork' // @ts-expect-error no type definition import solc from 'solc' function findImports(_path: string) { const filepath = getNodeModulesPackagePath(_path) const contents = fs.readFileSync(filepath).toString() return { contents } } class CreateERC20Token implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Create Token' this.name = 'createToken' this.icon = 'erc20.svg' this.type = 'action' this.category = 'Cryptocurrency' this.version = 1.0 this.description = 'Create new cryptocurrency token (ERC20)' this.incoming = 1 this.outgoing = 1 this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...ETHNetworks, ...PolygonNetworks, ...ArbitrumNetworks, ...OptimismNetworks, ...BSCNetworks], default: 'goerli' }, { label: 'Network Provider', name: 'networkProvider', type: 'asyncOptions', loadMethod: 'getNetworkProviders' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] this.inputParameters = [ { label: 'Select Wallet', name: 'wallet', type: 'asyncOptions', description: 'Wallet account to create ERC20 Token.', loadFromDbCollections: ['Wallet'], loadMethod: 'getWallets' }, { label: 'Token Name', name: 'tokenName', type: 'string', default: '', placeholder: 'MyToken' }, { label: 'Token Symbol', name: 'tokenSymbol', type: 'string', default: '', placeholder: 'MYT' }, { label: 'Token Supply', name: 'tokenSupply', type: 'number', default: 1000, description: 'Initialy supply of the token' }, { label: 'Solidity Version', name: 'solidityVersion', type: 'options', description: 'Soldity version to compile code for token creation', options: [ { label: '0.8.10', name: '0.8.10' }, { label: '0.8.11', name: '0.8.11' }, { label: '0.8.12', name: '0.8.12' }, { label: '0.8.13', name: '0.8.13' }, { label: '0.8.14', name: '0.8.14' }, { label: '0.8.15', name: '0.8.15' } ], default: '0.8.15' } ] as INodeParams[] } loadMethods = { async getWallets(nodeData: INodeData, dbCollection?: IDbCollection): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) { return returnData } try { if (dbCollection === undefined || !dbCollection || !dbCollection.Wallet) { return returnData } const wallets: IWallet[] = dbCollection.Wallet for (let i = 0; i < wallets.length; i += 1) { const wallet = wallets[i] const data = { label: `${wallet.name} (${wallet.network})`, name: JSON.stringify(wallet), description: wallet.address } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } }, async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK return getNetworkProvidersList(network) } } async run(nodeData: INodeData): Promise { const networksData = nodeData.networks const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (networksData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } try { const walletString = inputParametersData.wallet as string const walletDetails: IWallet = JSON.parse(walletString) const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') // Get wallet instance const walletCredential = JSON.parse(walletDetails.walletCredential) const wallet = new ethers.Wallet(walletCredential.privateKey as string, provider) const tokenName = inputParametersData.tokenName as string const tokenSupply = inputParametersData.tokenSupply as number const tokenSymbol = inputParametersData.tokenSymbol as string const solidityVersion = inputParametersData.solidityVersion as string const input = { language: 'Solidity', sources: {}, settings: { outputSelection: { '*': { '*': ['*'] } } } } as any const contractCode = `// SPDX-License-Identifier: MIT pragma solidity ^${solidityVersion}; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract ${tokenName.trim()} is ERC20 { constructor(uint256 initialSupply) ERC20("${tokenName} Token", "${tokenSymbol}"){ _mint(msg.sender, initialSupply); } }` input.sources[tokenName + '.sol'] = { content: contractCode } const output = JSON.parse(solc.compile(JSON.stringify(input), { import: findImports })) const contractOutput = output.contracts[tokenName + '.sol'] const contractName = Object.keys(contractOutput)[0] const bytecode = contractOutput[contractName].evm.bytecode.object const abi = contractOutput[contractName].abi const factory = new ethers.ContractFactory(abi, bytecode, wallet) const deployedContract = await factory.deploy(ethers.BigNumber.from(`${tokenSupply}000000000000000000`)) // The contract is NOT deployed yet; we must wait until it is mined await deployedContract.deployed() const returnItem: ICommonObject = { link: `${networkExplorers[network]}/address/${deployedContract.address}`, address: deployedContract.address, transactionHash: deployedContract.deployTransaction.hash } return returnNodeExecutionData(returnItem) } catch (e) { throw handleErrorMessage(e) } } } module.exports = { nodeClass: CreateERC20Token } ================================================ FILE: packages/components/nodes/CreateNFT/CreateNFT.ts ================================================ import { ICommonObject, IDbCollection, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, IWallet, NodeType } from '../../src/Interface' import { getNodeModulesPackagePath, handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import { ethers } from 'ethers' import * as fs from 'fs' import { ArbitrumNetworks, BSCNetworks, ETHNetworks, getNetworkProvider, getNetworkProvidersList, NETWORK, networkExplorers, networkProviderCredentials, NETWORK_PROVIDER, openseaExplorers, OptimismNetworks, PolygonNetworks } from '../../src/ChainNetwork' // @ts-expect-error no type definition import solc from 'solc' function findImports(_path: string) { const filepath = getNodeModulesPackagePath(_path) const contents = fs.readFileSync(filepath).toString() return { contents } } class CreateNFT implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Create NFT' this.name = 'createNFT' this.icon = 'createNFT.png' this.type = 'action' this.category = 'NFT' this.version = 1.0 this.description = 'Create new NFT (ERC1155)' this.incoming = 1 this.outgoing = 1 this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...ETHNetworks, ...PolygonNetworks, ...ArbitrumNetworks, ...OptimismNetworks, ...BSCNetworks], default: 'goerli' }, { label: 'Network Provider', name: 'networkProvider', type: 'asyncOptions', loadMethod: 'getNetworkProviders' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] this.inputParameters = [ { label: 'Select Wallet', name: 'wallet', type: 'asyncOptions', description: 'Wallet account to create NFT.', loadFromDbCollections: ['Wallet'], loadMethod: 'getWallets' }, { label: 'NFT Metadata', name: 'nftMetadata', type: 'options', options: [ { label: 'Ipfs Hash/Pin', name: 'ipfsHash', description: 'Ipfs hash/pin of the folder that contains the json metadata files' }, { label: 'URL', name: 'url', description: 'URL of the folder that contains the json metadata files. Ex: https://ipfs.io/ipfs/QmSPiKckfBDhw1pXdjHvU4jndN5pn4ZbKHeA9Nnn622C7U' } ], description: 'Fetch metadata from a url OR using Ipfs hash/pin' }, { label: 'NFT Metadata URL', name: 'nftMetadataJsonUrl', type: 'string', placeholder: 'https://ipfs.io/ipfs/QmSPiKckfBDhw1pXdjHvU4jndN5pn4ZbKHeA9Nnn622C7U', description: 'URL of the folder that contains the json metadata files', show: { 'inputParameters.nftMetadata': ['url'] } }, { label: 'NFT Metadata Ipfs Hash/Pin', name: 'nftMetadataHash', type: 'string', placeholder: 'QmexuwvmmtwsazQ7LK93SyVdFeYRnDbjET414y2xXiToM4', description: 'Ipfs hash/pin of the folder that contains the json metadata files', show: { 'inputParameters.nftMetadata': ['ipfsHash'] } }, { label: 'Contract Name', name: 'contractName', type: 'string', default: '', placeholder: 'MyContract', optional: true }, { label: 'Collection Name', name: 'collectionName', type: 'string', default: '', placeholder: 'MyCollection', optional: true }, { label: 'Solidity Version', name: 'solidityVersion', type: 'options', description: 'Soldity version to compile code for NFT creation', options: [ { label: '0.8.10', name: '0.8.10' }, { label: '0.8.11', name: '0.8.11' }, { label: '0.8.12', name: '0.8.12' }, { label: '0.8.13', name: '0.8.13' }, { label: '0.8.14', name: '0.8.14' }, { label: '0.8.15', name: '0.8.15' } ], default: '0.8.15' } ] as INodeParams[] } loadMethods = { async getWallets(nodeData: INodeData, dbCollection?: IDbCollection): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) { return returnData } try { if (dbCollection === undefined || !dbCollection || !dbCollection.Wallet) { return returnData } const wallets: IWallet[] = dbCollection.Wallet for (let i = 0; i < wallets.length; i += 1) { const wallet = wallets[i] const data = { label: `${wallet.name} (${wallet.network})`, name: JSON.stringify(wallet), description: wallet.address } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } }, async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK return getNetworkProvidersList(network) } } async run(nodeData: INodeData): Promise { function getRandomInt(max: number) { return Math.floor(Math.random() * max) } const networksData = nodeData.networks const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (networksData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } try { const walletString = inputParametersData.wallet as string const walletDetails: IWallet = JSON.parse(walletString) const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') // Get wallet instance const walletCredential = JSON.parse(walletDetails.walletCredential) const wallet = new ethers.Wallet(walletCredential.privateKey as string, provider) let nftContractName = (inputParametersData.contractName as string) || `ERC1155Contract${getRandomInt(10000)}` const collectionName = (inputParametersData.collectionName as string) || `Untilted Collection #${getRandomInt(10000)}` const nftMetadataJsonUrl = inputParametersData.nftMetadataJsonUrl as string const nftMetadataHash = inputParametersData.nftMetadataHash as string const nftSupply = 1 const solidityVersion = inputParametersData.solidityVersion as string const input = { language: 'Solidity', sources: {}, settings: { outputSelection: { '*': { '*': ['*'] } } } } as any let metadata = '' if (nftMetadataJsonUrl) { metadata = `${nftMetadataJsonUrl}/{id}.json` } else if (nftMetadataHash) { metadata = `ipfs://${nftMetadataHash}/{id}.json` } let encodePacked = '' if (metadata) { encodePacked = metadata.substring(0, metadata.lastIndexOf('/') + 1) } nftContractName = nftContractName.replace(/\s/g, '') const tokenId = 0 const contractCode = `// SPDX-License-Identifier: MIT pragma solidity ^${solidityVersion}; import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/security/Pausable.sol"; import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol"; import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; contract ${nftContractName} is ERC1155, Ownable, Pausable, ERC1155Burnable, ERC1155Supply { string public name; constructor() ERC1155("${metadata}") { name = "${collectionName}"; //collection name _mint(msg.sender, ${tokenId}, ${nftSupply}, ""); } // to Put NFT to Opensea function uri(uint256 _tokenId) override public view returns (string memory) { return string( abi.encodePacked( "${encodePacked}", Strings.toString(_tokenId), ".json" ) ); } function setURI(string memory newuri) public onlyOwner { _setURI(newuri); } function pause() public onlyOwner { _pause(); } function unpause() public onlyOwner { _unpause(); } function mint(address account, uint256 id, uint256 amount, bytes memory data) public onlyOwner { _mint(account, id, amount, data); } function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) public onlyOwner { _mintBatch(to, ids, amounts, data); } function _beforeTokenTransfer(address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) internal whenNotPaused override(ERC1155, ERC1155Supply) { super._beforeTokenTransfer(operator, from, to, ids, amounts, data); } }` input.sources[nftContractName + '.sol'] = { content: contractCode } const output = JSON.parse(solc.compile(JSON.stringify(input), { import: findImports })) const contractOutput = output.contracts[nftContractName + '.sol'] const contractName = Object.keys(contractOutput)[0] const bytecode = contractOutput[contractName].evm.bytecode.object const abi = contractOutput[contractName].abi const factory = new ethers.ContractFactory(abi, bytecode, wallet) const deployedContract = await factory.deploy() // The contract is NOT deployed yet; we must wait until it is mined await deployedContract.deployed() const returnItem: ICommonObject = { explorerLink: `${networkExplorers[network]}/address/${deployedContract.address}`, openseaLink: `${openseaExplorers[network]}/assets/${deployedContract.address}/${tokenId}`, address: deployedContract.address, transactionHash: deployedContract.deployTransaction.hash } return returnNodeExecutionData(returnItem) } catch (e) { throw handleErrorMessage(e) } } } module.exports = { nodeClass: CreateNFT } ================================================ FILE: packages/components/nodes/Discord/Discord.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' interface IDiscordWebhook { content?: string username?: string avatar_url?: string tts?: boolean } class Discord implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number inputParameters?: INodeParams[] constructor() { this.label = 'Discord' this.name = 'discord' this.icon = 'discord.svg' this.type = 'action' this.category = 'Communication' this.version = 1.0 this.description = 'Post message in Discord channel' this.incoming = 1 this.outgoing = 1 this.inputParameters = [ { label: 'Webhook URL', name: 'webhookUrl', type: 'string', default: '', description: 'Webhook URL for the channel. Learn how to get: https://www.youtube.com/watch?v=K8vgRWZnSZw' }, { label: 'Content', description: 'Message contents (up to 2000 characters)', name: 'content', type: 'string', default: '' }, { label: 'Username', name: 'username', type: 'string', default: '', description: 'Override the default username of the webhook', optional: true }, { label: 'Avatar URL', name: 'avatarUrl', type: 'string', default: '', description: 'Override the default avatar of the webhook', optional: true }, { label: 'TTS', name: 'tts', type: 'boolean', default: false, description: 'Send as Text To Speech message', optional: true } ] as INodeParams[] } async run(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters if (inputParametersData === undefined) { throw new Error('Required data missing') } const returnData: ICommonObject[] = [] const body: IDiscordWebhook = {} const webhookUrl = inputParametersData.webhookUrl as string const content = inputParametersData.content as string body.content = content if (inputParametersData.username) body.username = inputParametersData.username as string if (inputParametersData.avatarUrl) body.avatar_url = inputParametersData.avatarUrl as string if (inputParametersData.tts) body.tts = inputParametersData.tts as boolean let responseData: any let maxRetries = 5 do { try { const axiosConfig: AxiosRequestConfig = { method: 'POST' as Method, url: `${webhookUrl}?wait=true`, data: body, headers: { 'Content-Type': 'application/json; charset=utf-8' } } const response = await axios(axiosConfig) responseData = response.data break } catch (error) { // Rate limit exceeded if (error.response && error.response.status === 429) { const retryAfter = error.response?.headers['retry-after'] || 60 await new Promise((resolve, _) => { setTimeout(() => { resolve() }, retryAfter * 1000) }) continue } throw handleErrorMessage(error) } } while (--maxRetries) if (maxRetries <= 0) { throw new Error('Error posting message to discord channel. Max retries limit was reached.') } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: Discord } ================================================ FILE: packages/components/nodes/ERC20Function/ERC20Function.ts ================================================ import { ICommonObject, IDbCollection, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, IWallet, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import { ArbitrumNetworks, chainIdLookup, ETHNetworks, getNetworkProvider, getNetworkProvidersList, NETWORK, networkExplorers, networkProviderCredentials, NETWORK_PROVIDER, OptimismNetworks, PolygonNetworks } from '../../src' import { ContractInterface, ethers } from 'ethers' import axios, { AxiosRequestConfig, Method } from 'axios' import { IToken } from '../Uniswap/nativeTokens' import { allowanceParameters, approveParameters, balanceOfParameters, depositParameters, ERC20Functions, transferFromParameters, withdrawParameters } from './helperFunctions' import IERC20 from '../../src/abis/WETH.json' import IWETH from '@uniswap/v2-periphery/build/IWETH.json' class ERC20Function implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'ERC20 Function' this.name = 'ERC20Function' this.icon = 'erc20.svg' this.type = 'action' this.category = 'Cryptocurrency' this.version = 1.0 this.description = 'Execute ERC20 function such as deposit, withdraw, get balance, etc' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Function', name: 'function', type: 'options', options: [...ERC20Functions] } ] as INodeParams[] this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...ETHNetworks, ...PolygonNetworks, ...OptimismNetworks, ...ArbitrumNetworks] }, { label: 'Network Provider', name: 'networkProvider', type: 'asyncOptions', loadMethod: 'getNetworkProviders' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] this.inputParameters = [ { label: 'ERC20 Token', name: 'erc20Token', type: 'asyncOptions', description: 'ERC20 Token to send/transfer', loadMethod: 'getTokens' }, { label: 'Custom ERC20 Address', name: 'customERC20TokenAddress', type: 'string', description: 'ERC20 Token Address', show: { 'inputParameters.erc20Token': ['customERC20Address'] } }, ...approveParameters, ...allowanceParameters, ...balanceOfParameters, ...transferFromParameters, ...depositParameters, ...withdrawParameters, { label: 'Gas Limit', name: 'gasLimit', type: 'number', optional: true, placeholder: '100000', description: 'Maximum price you are willing to pay when sending a transaction', show: { 'actions.function': ['approve', 'transferFrom', 'deposit'] } }, { label: 'Max Fee per Gas', name: 'maxFeePerGas', type: 'number', optional: true, placeholder: '200', description: 'The maximum price (in wei) per unit of gas for transaction. See more', show: { 'actions.function': ['approve', 'transferFrom', 'deposit'] } }, { label: 'Max Priority Fee per Gas', name: 'maxPriorityFeePerGas', type: 'number', optional: true, placeholder: '5', description: 'The priority fee price (in wei) per unit of gas for transaction. See more', show: { 'actions.function': ['approve', 'transferFrom', 'deposit'] } } ] as INodeParams[] } loadMethods = { async getWallets(nodeData: INodeData, dbCollection?: IDbCollection): Promise { const returnData: INodeOptionsValue[] = [] try { if (dbCollection === undefined || !dbCollection || !dbCollection.Wallet) { return returnData } const wallets: IWallet[] = dbCollection.Wallet for (let i = 0; i < wallets.length; i += 1) { const wallet = wallets[i] const data = { label: `${wallet.name} (${wallet.network})`, name: JSON.stringify(wallet), description: wallet.address } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } }, async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK return getNetworkProvidersList(network) }, async getTokens(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://tokens.uniswap.org` } const response = await axios(axiosConfig) const responseData = response.data let tokens: IToken[] = responseData.tokens // Add custom token const data = { label: `- Custom ERC20 Address -`, name: `customERC20Address` } as INodeOptionsValue returnData.push(data) // Add other tokens tokens = tokens.filter((tkn) => tkn.chainId === chainIdLookup[network]) for (let i = 0; i < tokens.length; i += 1) { const token = tokens[i] const data = { label: `${token.name} (${token.symbol})`, name: `${token.address};${token.decimals}` } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } } } async run(nodeData: INodeData): Promise { const networksData = nodeData.networks const credentials = nodeData.credentials const actionsData = nodeData.actions const inputParametersData = nodeData.inputParameters if (networksData === undefined || inputParametersData === undefined || actionsData === undefined) { throw new Error('Required data missing') } try { const erc20Function = actionsData.function as string const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') const owner = inputParametersData.owner as string const spender = inputParametersData.spender as string const amount = inputParametersData.amount as string const account = inputParametersData.account as string const from = inputParametersData.from as string const to = inputParametersData.to as string const erc20Token = inputParametersData.erc20Token as string const customERC20TokenAddress = inputParametersData.customERC20TokenAddress as string const contractAddress = erc20Token === 'customERC20Address' ? customERC20TokenAddress : erc20Token.split(';')[0] const contractInstance = new ethers.Contract(contractAddress, IERC20, provider) const decimals = erc20Token === 'customERC20Address' ? parseInt(await contractInstance.decimals(), 10) : parseInt(erc20Token.split(';').pop() || '0', 10) let returnItem = { function: erc20Function, link: `${networkExplorers[network]}/address/${contractAddress}` } as any if (erc20Function === 'allowance') { // allowance(address owner, address spender) → uint256 returnItem.result = await contractInstance.allowance(owner, spender) } else if (erc20Function === 'approve') { // approve(address spender, uint256 amount) → bool const { txOption, wallet } = await getWalletSigner(inputParametersData, provider) const functionApproveAbi = ['function approve(address spender, uint256 amount) external returns (boolean)'] const contractInstance = new ethers.Contract(contractAddress, functionApproveAbi, wallet) const numberOfTokens = ethers.utils.parseUnits(amount, decimals) const tx = await contractInstance.approve(spender, numberOfTokens, txOption) const txReceipt = await tx.wait() returnItem = { function: erc20Function, spender, amount, transactionHash: tx.hash, transactionReceipt: txReceipt as any, link: `${networkExplorers[network]}/tx/${tx.hash}` } } else if (erc20Function === 'balanceOf') { // balanceOf(address account) → uint256 returnItem.result = ethers.utils.formatEther(await contractInstance.balanceOf(account)) } else if (erc20Function === 'decimals') { // decimals() → uint8 returnItem.result = await contractInstance.decimals() } else if (erc20Function === 'name') { // name() → string returnItem.result = await contractInstance.name() } else if (erc20Function === 'symbol') { // symbol() → string returnItem.result = await contractInstance.symbol() } else if (erc20Function === 'totalSupply') { // totalSupply() → uint256 returnItem.result = ethers.utils.formatEther(await contractInstance.totalSupply()) } else if (erc20Function === 'transferFrom') { // transferFrom(address sender, address recipient, uint256 amount) → bool const { txOption, wallet } = await getWalletSigner(inputParametersData, provider) const functionTransferFromAbi = [ 'function transferFrom(address sender, address recipient, uint256 amount) external returns (boolean)' ] const contractInstance = new ethers.Contract(contractAddress, functionTransferFromAbi, wallet) const numberOfTokens = ethers.utils.parseUnits(amount, decimals) const tx = await contractInstance.transferFrom(from, to, numberOfTokens, txOption) const txReceipt = await tx.wait() returnItem = { function: erc20Function, transferFrom: from, transferTo: to, amount, transactionHash: tx.hash, transactionReceipt: txReceipt as any, link: `${networkExplorers[network]}/tx/${tx.hash}` } } else if (erc20Function === 'deposit') { const { txOption, wallet } = await getWalletSigner(inputParametersData, provider) const wrapEthContract = new ethers.Contract(contractAddress, IWETH['abi'] as ContractInterface, wallet) const numberOfTokens = ethers.utils.parseUnits(amount, decimals) const tx = await wrapEthContract.deposit({ ...txOption, value: numberOfTokens }) const txReceipt = await tx.wait() if (txReceipt.status === 0) throw new Error(`Failed to deposit ETH to ${contractAddress}`) returnItem = { function: erc20Function, amount, transactionHash: tx.hash, transactionReceipt: txReceipt as any, link: `${networkExplorers[network]}/tx/${tx.hash}` } } else if (erc20Function === 'withdraw') { const { wallet } = await getWalletSigner(inputParametersData, provider) const wrapEthContract = new ethers.Contract(contractAddress, IWETH['abi'] as ContractInterface, wallet) const numberOfTokens = ethers.utils.parseUnits(amount, decimals) const tx = await wrapEthContract.withdraw(numberOfTokens) const txReceipt = await tx.wait() if (txReceipt.status === 0) throw new Error(`Failed to withdraw ETH from ${contractAddress}`) returnItem = { function: erc20Function, amount, transactionHash: tx.hash, transactionReceipt: txReceipt as any, link: `${networkExplorers[network]}/tx/${tx.hash}` } } return returnNodeExecutionData(returnItem) } catch (error) { throw handleErrorMessage(error) } } } const getWalletSigner = async ( inputParametersData: ICommonObject, provider: ethers.providers.JsonRpcProvider | ethers.providers.FallbackProvider ) => { const walletString = inputParametersData.wallet as string const walletDetails: IWallet = JSON.parse(walletString) const walletCredential = JSON.parse(walletDetails.walletCredential) const gasLimit = inputParametersData.gasLimit as number const maxFeePerGas = inputParametersData.maxFeePerGas as number const maxPriorityFeePerGas = inputParametersData.maxPriorityFeePerGas as number const wallet = new ethers.Wallet(walletCredential.privateKey as string, provider) const txOption = {} as any txOption.nonce = await provider.getTransactionCount(walletDetails.address) if (gasLimit) txOption.gasLimit = gasLimit if (maxFeePerGas) txOption.maxFeePerGas = maxFeePerGas if (maxPriorityFeePerGas) txOption.maxPriorityFeePerGas = maxPriorityFeePerGas return { txOption, wallet } } module.exports = { nodeClass: ERC20Function } ================================================ FILE: packages/components/nodes/ERC20Function/helperFunctions.ts ================================================ import { INodeOptionsValue, INodeParams } from '../../src' export const ERC20Functions = [ { name: 'deposit', label: 'Deposit', description: 'Deposit ETH into ERC20 token' }, { name: 'withdraw', label: 'Withdraw', description: 'Withdraw ETH from ERC20 token' }, { name: 'allowance', label: 'Get Allowance', description: 'Returns the remaining number of tokens that spender will be allowed to spend on behalf of owner through transferFrom. This is zero by default.' }, { name: 'approve', label: 'Approve', description: 'Sets amount as the allowance of spender over the caller’s tokens.' }, { name: 'balanceOf', label: 'Get Balance', description: 'Returns the amount of tokens owned by account' }, { name: 'decimals', label: 'Get ERC20 Decimals', description: 'Returns the decimals of ERC20' }, { name: 'name', label: 'Get ERC20 Name', description: 'Returns the name of ERC20' }, { name: 'symbol', label: 'Get ERC20 Symbol', description: 'Returns the symbol of ERC20' }, { name: 'totalSupply', label: 'Get ERC20 Total Supply', description: 'Returns the total supply of ERC20' }, { name: 'transferFrom', label: 'Transfer From', description: 'Moves amount tokens from sender to recipient using the allowance mechanism. Amount is then deducted from the caller’s allowance.' } ] as INodeOptionsValue[] export const allowanceParameters = [ { label: 'Owner Address', name: 'owner', type: 'string', show: { 'actions.function': ['allowance'] } }, { label: 'Spender Address', name: 'spender', type: 'string', show: { 'actions.function': ['allowance'] } } ] as INodeParams[] export const approveParameters = [ { label: 'Spender Address', name: 'spender', type: 'string', show: { 'actions.function': ['approve'] } }, { label: 'Amount', name: 'amount', type: 'number', show: { 'actions.function': ['approve'] } }, { label: 'Select Wallet', name: 'wallet', type: 'asyncOptions', description: 'Wallet to execute approve function', loadFromDbCollections: ['Wallet'], loadMethod: 'getWallets', show: { 'actions.function': ['approve'] } } ] as INodeParams[] export const balanceOfParameters = [ { label: 'Account Address', name: 'account', type: 'string', description: 'Account address to check for remaining amount', show: { 'actions.function': ['balanceOf'] } } ] as INodeParams[] export const transferFromParameters = [ { label: 'From Address', name: 'from', type: 'string', description: 'Account address to transfer the token', show: { 'actions.function': ['transferFrom'] } }, { label: 'To Address', name: 'to', type: 'string', description: 'Account address to receive the token', show: { 'actions.function': ['transferFrom'] } }, { label: 'Amount', name: 'amount', type: 'number', description: 'Amount of token transfer', show: { 'actions.function': ['transferFrom'] } }, { label: 'Select Wallet', name: 'wallet', type: 'asyncOptions', description: 'Wallet to move amount tokens from sender to recipient using the allowance mechanism', loadFromDbCollections: ['Wallet'], loadMethod: 'getWallets', show: { 'actions.function': ['transferFrom'] } } ] as INodeParams[] export const depositParameters = [ { label: 'Amount', name: 'amount', type: 'number', description: 'Amount of token to deposit', show: { 'actions.function': ['deposit'] } }, { label: 'Select Wallet', name: 'wallet', type: 'asyncOptions', description: 'Wallet to deposit ETH into ERC20 Token', loadFromDbCollections: ['Wallet'], loadMethod: 'getWallets', show: { 'actions.function': ['deposit'] } } ] as INodeParams[] export const withdrawParameters = [ { label: 'Amount', name: 'amount', type: 'number', description: 'Amount of token to withdraw', show: { 'actions.function': ['withdraw'] } }, { label: 'Select Wallet', name: 'wallet', type: 'asyncOptions', description: 'Withdraw ERC20 Token to this wallet', loadFromDbCollections: ['Wallet'], loadMethod: 'getWallets', show: { 'actions.function': ['withdraw'] } } ] as INodeParams[] ================================================ FILE: packages/components/nodes/ERC20Transfer/ERC20Transfer.ts ================================================ import { IDbCollection, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, IWallet, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import { ArbitrumNetworks, chainIdLookup, ETHNetworks, functionTransferAbi, getNetworkProvider, getNetworkProvidersList, NETWORK, networkExplorers, networkProviderCredentials, NETWORK_PROVIDER, OptimismNetworks, PolygonNetworks } from '../../src' import { ethers } from 'ethers' import axios, { AxiosRequestConfig, Method } from 'axios' import { IToken } from '../Uniswap/nativeTokens' import IERC20 from '../../src/abis/WETH.json' class ERC20Transfer implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'ERC20 Transfer' this.name = 'ERC20Transfer' this.icon = 'erc20.svg' this.type = 'action' this.category = 'Cryptocurrency' this.version = 1.0 this.description = 'Send/Transfer ERC20 to an address' this.incoming = 1 this.outgoing = 1 this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...ETHNetworks, ...PolygonNetworks, ...OptimismNetworks, ...ArbitrumNetworks] }, { label: 'Network Provider', name: 'networkProvider', type: 'asyncOptions', loadMethod: 'getNetworkProviders' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] this.inputParameters = [ { label: 'ERC20 Token', name: 'erc20Token', type: 'asyncOptions', description: 'ERC20 Token to send/transfer', loadMethod: 'getTokens' }, { label: 'Custom ERC20 Address', name: 'customERC20TokenAddress', type: 'string', description: 'ERC20 Token Address', show: { 'inputParameters.erc20Token': ['customERC20Address'] } }, { label: 'Wallet To Transfer', name: 'wallet', type: 'asyncOptions', description: 'Wallet account to send/transfer ERC20', loadFromDbCollections: ['Wallet'], loadMethod: 'getWallets' }, { label: 'Address To Receive', name: 'address', type: 'string', default: '', description: 'Address to receive ERC20' }, { label: 'Amount', name: 'amount', type: 'number', description: 'Amount of ERC20 to transfer' }, { label: 'Gas Limit', name: 'gasLimit', type: 'number', optional: true, placeholder: '100000', description: 'Maximum price you are willing to pay when sending a transaction' }, { label: 'Max Fee per Gas', name: 'maxFeePerGas', type: 'number', optional: true, placeholder: '200', description: 'The maximum price (in wei) per unit of gas for transaction. See more' }, { label: 'Max Priority Fee per Gas', name: 'maxPriorityFeePerGas', type: 'number', optional: true, placeholder: '5', description: 'The priority fee price (in wei) per unit of gas for transaction. See more' } ] as INodeParams[] } loadMethods = { async getWallets(nodeData: INodeData, dbCollection?: IDbCollection): Promise { const returnData: INodeOptionsValue[] = [] try { if (dbCollection === undefined || !dbCollection || !dbCollection.Wallet) { return returnData } const wallets: IWallet[] = dbCollection.Wallet for (let i = 0; i < wallets.length; i += 1) { const wallet = wallets[i] const data = { label: `${wallet.name} (${wallet.network})`, name: JSON.stringify(wallet), description: wallet.address } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } }, async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK return getNetworkProvidersList(network) }, async getTokens(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://tokens.uniswap.org` } const response = await axios(axiosConfig) const responseData = response.data let tokens: IToken[] = responseData.tokens // Add custom token const data = { label: `- Custom ERC20 Address -`, name: `customERC20Address` } as INodeOptionsValue returnData.push(data) // Add other tokens tokens = tokens.filter((tkn) => tkn.chainId === chainIdLookup[network]) for (let i = 0; i < tokens.length; i += 1) { const token = tokens[i] const data = { label: `${token.name} (${token.symbol})`, name: `${token.address};${token.decimals}` } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } } } async run(nodeData: INodeData): Promise { const networksData = nodeData.networks const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (networksData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } try { const walletString = inputParametersData.wallet as string const walletDetails: IWallet = JSON.parse(walletString) const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') // Get wallet instance const walletCredential = JSON.parse(walletDetails.walletCredential) const wallet = new ethers.Wallet(walletCredential.privateKey as string, provider) const address = inputParametersData.address as string const amount = inputParametersData.amount as string const erc20Token = inputParametersData.erc20Token as string const customERC20TokenAddress = inputParametersData.customERC20TokenAddress as string const gasLimit = inputParametersData.gasLimit as number const maxFeePerGas = inputParametersData.maxFeePerGas as number const maxPriorityFeePerGas = inputParametersData.maxPriorityFeePerGas as number const contractAddress = erc20Token === 'customERC20Address' ? customERC20TokenAddress : erc20Token.split(';')[0] const contractInstance = new ethers.Contract(contractAddress, IERC20, wallet) const decimals = erc20Token === 'customERC20Address' ? parseInt(await contractInstance.decimals(), 10) : parseInt(erc20Token.split(';').pop() || '0', 10) const numberOfTokens = ethers.utils.parseUnits(amount, decimals) // Send token const nonce = await provider.getTransactionCount(walletDetails.address) const txOption = { nonce } as any if (gasLimit) txOption.gasLimit = gasLimit if (maxFeePerGas) txOption.maxFeePerGas = maxFeePerGas if (maxPriorityFeePerGas) txOption.maxPriorityFeePerGas = maxPriorityFeePerGas const contractInstanceForTransfer = new ethers.Contract(contractAddress, functionTransferAbi, wallet) const tx = await contractInstanceForTransfer.transfer(address, numberOfTokens, txOption) const txReceipt = await tx.wait() const returnItem = { transferFrom: wallet.address, transferTo: address, amount, transactionHash: tx.hash, transactionReceipt: txReceipt as any, link: `${networkExplorers[network]}/tx/${tx.hash}` } return returnNodeExecutionData(returnItem) } catch (error) { throw handleErrorMessage(error) } } } module.exports = { nodeClass: ERC20Transfer } ================================================ FILE: packages/components/nodes/ERC20TransferTrigger/ERC20TransferTrigger.ts ================================================ import { BigNumber, ethers, utils } from 'ethers' import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams, IProviders, NodeType } from '../../src/Interface' import { returnNodeExecutionData } from '../../src/utils' import EventEmitter from 'events' import { ArbitrumNetworks, ETHNetworks, OptimismNetworks, PolygonNetworks, networkExplorers, getNetworkProvidersList, NETWORK, getNetworkProvider, NETWORK_PROVIDER, eventTransferAbi, networkProviderCredentials, chainIdLookup } from '../../src/ChainNetwork' import IERC20 from '../../src/abis/WETH.json' import axios, { AxiosRequestConfig, Method } from 'axios' import { IToken } from '../PancakeSwap/extendedTokens' class ERC20TransferTrigger extends EventEmitter implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] providers: IProviders constructor() { super() this.label = 'ERC20 Transfer Trigger' this.name = 'ERC20TransferTrigger' this.icon = 'erc20.svg' this.type = 'trigger' this.category = 'Cryptocurrency' this.version = 1.1 this.description = 'Start workflow whenever an ERC20 transfer event happened' this.incoming = 0 this.outgoing = 1 this.providers = {} this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...ETHNetworks, ...PolygonNetworks, ...ArbitrumNetworks, ...OptimismNetworks], default: 'homestead' }, { label: 'Network Provider', name: 'networkProvider', type: 'asyncOptions', loadMethod: 'getNetworkProviders' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] this.inputParameters = [ { label: 'ERC20 Token', name: 'erc20Token', type: 'asyncOptions', description: 'ERC20 Token to send/transfer', loadMethod: 'getTokens', default: 'anyERC20Address' }, { label: 'Custom ERC20 Address', name: 'customERC20TokenAddress', type: 'string', description: 'ERC20 Token Address', show: { 'inputParameters.erc20Token': ['customERC20Address'] } }, { label: 'Direction', name: 'direction', type: 'options', options: [ { label: 'From', name: 'from', description: 'Transfer from wallet address' }, { label: 'To', name: 'to', description: 'Transfer to wallet address' }, { label: 'Both From and To', name: 'fromTo', description: 'Transfer from a wallet address to another wallet address' } ], default: '' }, { label: 'From Wallet Address', name: 'fromAddress', type: 'string', default: '', show: { 'inputParameters.direction': ['from', 'fromTo'] } }, { label: 'To Wallet Address', name: 'toAddress', type: 'string', default: '', show: { 'inputParameters.direction': ['to', 'fromTo'] } } ] as INodeParams[] } loadMethods = { async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK return getNetworkProvidersList(network) }, async getTokens(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://tokens.uniswap.org` } const response = await axios(axiosConfig) const responseData = response.data let tokens: IToken[] = responseData.tokens // Add any token const anyData = { label: `- Any ERC20 Token -`, name: `anyERC20Address` } as INodeOptionsValue returnData.push(anyData) // Add custom token const data = { label: `- Custom ERC20 Address -`, name: `customERC20Address` } as INodeOptionsValue returnData.push(data) // Add other tokens tokens = tokens.filter((tkn) => tkn.chainId === chainIdLookup[network]) for (let i = 0; i < tokens.length; i += 1) { const token = tokens[i] const data = { label: `${token.name} (${token.symbol})`, name: `${token.address};${token.decimals}` } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } } } async runTrigger(nodeData: INodeData): Promise { const networksData = nodeData.networks const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (networksData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') const emitEventKey = nodeData.emitEventKey as string const fromAddress = (inputParametersData.fromAddress as string) || null const toAddress = (inputParametersData.toAddress as string) || null const erc20Token = inputParametersData.erc20Token as string const customERC20TokenAddress = inputParametersData.customERC20TokenAddress as string const filter = { topics: [ utils.id('Transfer(address,address,uint256)'), fromAddress ? utils.hexZeroPad(fromAddress, 32) : null, toAddress ? utils.hexZeroPad(toAddress, 32) : null ] } as any if (erc20Token !== 'anyERC20Address') { filter['address'] = erc20Token === 'customERC20Address' ? customERC20TokenAddress : erc20Token.split(';')[0] } provider.on(filter, async (log: any) => { const txHash = log.transactionHash const contractInstance = new ethers.Contract(log.address, IERC20, provider) const iface = new ethers.utils.Interface(eventTransferAbi) const logs = await provider.getLogs(filter) const events = logs.map((log) => iface.parseLog(log)) const fromWallet = events.length ? events[0].args[0] : '' const toWallet = events.length ? events[0].args[1] : '' const value: BigNumber = events.length ? events[0].args[2] : '' //ERC20 has 3 topics length if (log.topics.length === 3) { const returnItem = {} as ICommonObject const name = await contractInstance.name() const symbol = await contractInstance.symbol() const decimals = await contractInstance.decimals() const amount = utils.formatUnits(value.toString(), decimals) returnItem['Token Name'] = name returnItem['Token Symbol'] = symbol returnItem['Token Address'] = log.address returnItem['From Wallet'] = fromWallet returnItem['To Wallet'] = toWallet returnItem['Amount Transfered'] = parseFloat(amount) returnItem['txHash'] = txHash returnItem['explorerLink'] = `${networkExplorers[network]}/tx/${txHash}` this.emit(emitEventKey, returnNodeExecutionData(returnItem)) } }) this.providers[emitEventKey] = { provider, filter } } async removeTrigger(nodeData: INodeData): Promise { const emitEventKey = nodeData.emitEventKey as string if (Object.prototype.hasOwnProperty.call(this.providers, emitEventKey)) { const provider = this.providers[emitEventKey].provider const filter = this.providers[emitEventKey].filter provider.off(filter) this.removeAllListeners(emitEventKey) } } } module.exports = { nodeClass: ERC20TransferTrigger } ================================================ FILE: packages/components/nodes/ETHBalanceTrigger/ETHBalanceTrigger.ts ================================================ import { CronJob } from 'cron' import { BigNumber, utils } from 'ethers' import { ICronJobs, INode, INodeData, INodeOptionsValue, INodeParams, NodeType } from '../../src/Interface' import { returnNodeExecutionData } from '../../src/utils' import EventEmitter from 'events' import { ArbitrumNetworks, ETHNetworks, getNetworkProvider, getNetworkProvidersList, NETWORK, networkExplorers, networkProviderCredentials, NETWORK_PROVIDER, OptimismNetworks } from '../../src/ChainNetwork' class ETHBalanceTrigger extends EventEmitter implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] cronJobs: ICronJobs constructor() { super() this.label = 'ETH Balance Trigger' this.name = 'ETHBalanceTrigger' this.icon = 'ethereum.svg' this.type = 'trigger' this.category = 'Cryptocurrency' this.version = 1.0 this.description = 'Start workflow whenever ETH balance in wallet changes' this.incoming = 0 this.outgoing = 1 this.cronJobs = {} this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...ETHNetworks, ...ArbitrumNetworks, ...OptimismNetworks], default: 'homestead' }, { label: 'Network Provider', name: 'networkProvider', type: 'asyncOptions', loadMethod: 'getNetworkProviders' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] this.inputParameters = [ { label: 'Wallet Address', name: 'address', type: 'string', default: '' }, { label: 'Trigger Condition', name: 'triggerCondition', type: 'options', options: [ { label: 'When balance increased', name: 'increase' }, { label: 'When balance decreased', name: 'decrease' } ], default: 'increase' }, { label: 'Polling Time', name: 'pollTime', type: 'options', options: [ { label: 'Every 15 secs', name: '15s' }, { label: 'Every 30 secs', name: '30s' }, { label: 'Every 1 min', name: '1min' }, { label: 'Every 5 min', name: '5min' }, { label: 'Every 10 min', name: '10min' } ], default: '30s' } ] as INodeParams[] } loadMethods = { async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK return getNetworkProvidersList(network) } } async runTrigger(nodeData: INodeData): Promise { const networksData = nodeData.networks const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (networksData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') const emitEventKey = nodeData.emitEventKey as string const address = (inputParametersData.address as string) || '' const pollTime = (inputParametersData.pollTime as string) || '30s' const triggerCondition = (inputParametersData.triggerCondition as string) || 'increase' const cronTimes: string[] = [] if (pollTime === '15s') { cronTimes.push(`*/15 * * * * *`) } else if (pollTime === '30s') { cronTimes.push(`*/30 * * * * *`) } else if (pollTime === '1min') { cronTimes.push(`*/1 * * * *`) } else if (pollTime === '5min') { cronTimes.push(`*/5 * * * *`) } else if (pollTime === '10min') { cronTimes.push(`*/10 * * * *`) } let lastBalance: BigNumber = await provider.getBalance(address) const executeTrigger = async () => { const newBalance: BigNumber = await provider.getBalance(address) if (!newBalance.eq(lastBalance)) { if (triggerCondition === 'increase' && newBalance.gt(lastBalance)) { const balanceInEth = utils.formatEther(BigNumber.from(newBalance.toString())) const diffInEth = newBalance.sub(lastBalance) const returnItem = { newBalance: `${balanceInEth} ETH`, lastBalance: `${utils.formatEther(BigNumber.from(lastBalance.toString()))} ETH`, difference: `${utils.formatEther(BigNumber.from(diffInEth.toString()))} ETH`, explorerLink: `${networkExplorers[network]}/address/${address}`, triggerCondition: 'ETH balance increase' } lastBalance = newBalance this.emit(emitEventKey, returnNodeExecutionData(returnItem)) } else if (triggerCondition === 'decrease' && newBalance.lt(lastBalance)) { const balanceInEth = utils.formatEther(BigNumber.from(newBalance.toString())) const diffInEth = lastBalance.sub(newBalance) const returnItem = { newBalance: `${balanceInEth} ETH`, lastBalance: `${utils.formatEther(BigNumber.from(lastBalance.toString()))} ETH`, difference: `${utils.formatEther(BigNumber.from(diffInEth.toString()))} ETH`, explorerLink: `${networkExplorers[network]}/address/${address}`, triggerCondition: 'ETH balance decrease' } lastBalance = newBalance this.emit(emitEventKey, returnNodeExecutionData(returnItem)) } else { lastBalance = newBalance } } } // Start the cron-jobs if (Object.prototype.hasOwnProperty.call(this.cronJobs, emitEventKey)) { for (const cronTime of cronTimes) { // Automatically start the cron job this.cronJobs[emitEventKey].push(new CronJob(cronTime, executeTrigger, undefined, true)) } } else { for (const cronTime of cronTimes) { // Automatically start the cron job this.cronJobs[emitEventKey] = [new CronJob(cronTime, executeTrigger, undefined, true)] } } } async removeTrigger(nodeData: INodeData): Promise { const emitEventKey = nodeData.emitEventKey as string if (Object.prototype.hasOwnProperty.call(this.cronJobs, emitEventKey)) { const cronJobs = this.cronJobs[emitEventKey] for (const cronJob of cronJobs) { cronJob.stop() } this.removeAllListeners(emitEventKey) } } } module.exports = { nodeClass: ETHBalanceTrigger } ================================================ FILE: packages/components/nodes/ETHTransfer/ETHTransfer.ts ================================================ import { IDbCollection, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, IWallet, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import { ArbitrumNetworks, ETHNetworks, getNetworkProvider, getNetworkProvidersList, NETWORK, networkExplorers, networkProviderCredentials, NETWORK_PROVIDER, OptimismNetworks } from '../../src' import { ethers } from 'ethers' class ETHTransfer implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'ETH Transfer' this.name = 'ETHTransfer' this.icon = 'ethereum.svg' this.type = 'action' this.category = 'Cryptocurrency' this.version = 1.0 this.description = 'Send/Transfer ETH to an address' this.incoming = 1 this.outgoing = 1 this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...ETHNetworks, ...OptimismNetworks, ...ArbitrumNetworks] }, { label: 'Network Provider', name: 'networkProvider', type: 'asyncOptions', loadMethod: 'getNetworkProviders' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] this.inputParameters = [ { label: 'Wallet To Transfer', name: 'wallet', type: 'asyncOptions', description: 'Wallet account to send/transfer ETH', loadFromDbCollections: ['Wallet'], loadMethod: 'getWallets' }, { label: 'Address To Receive', name: 'address', type: 'string', default: '', description: 'Address to receive ETH. Can be an ens name: username.eth' }, { label: 'Amount', name: 'amount', type: 'number', description: 'Amount of ETH to transfer' }, { label: 'Gas Limit', name: 'gasLimit', type: 'number', optional: true, placeholder: '100000', description: 'Maximum price you are willing to pay when sending a transaction' }, { label: 'Max Fee per Gas', name: 'maxFeePerGas', type: 'number', optional: true, placeholder: '200', description: 'The maximum price (in wei) per unit of gas for transaction. See more' }, { label: 'Max Priority Fee per Gas', name: 'maxPriorityFeePerGas', type: 'number', optional: true, placeholder: '5', description: 'The priority fee price (in wei) per unit of gas for transaction. See more' } ] as INodeParams[] } loadMethods = { async getWallets(nodeData: INodeData, dbCollection?: IDbCollection): Promise { const returnData: INodeOptionsValue[] = [] try { if (dbCollection === undefined || !dbCollection || !dbCollection.Wallet) { return returnData } const wallets: IWallet[] = dbCollection.Wallet for (let i = 0; i < wallets.length; i += 1) { const wallet = wallets[i] const data = { label: `${wallet.name} (${wallet.network})`, name: JSON.stringify(wallet), description: wallet.address } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } }, async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK return getNetworkProvidersList(network) } } async run(nodeData: INodeData): Promise { const networksData = nodeData.networks const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (networksData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } try { const walletString = inputParametersData.wallet as string const walletDetails: IWallet = JSON.parse(walletString) const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') // Get wallet instance const walletCredential = JSON.parse(walletDetails.walletCredential) const wallet = new ethers.Wallet(walletCredential.privateKey as string, provider) const address = inputParametersData.address as string const amount = inputParametersData.amount as string const gasLimit = inputParametersData.gasLimit as number const maxFeePerGas = inputParametersData.maxFeePerGas as number const maxPriorityFeePerGas = inputParametersData.maxPriorityFeePerGas as number // Send token const nonce = await provider.getTransactionCount(walletDetails.address) const txOption = { nonce } as any if (gasLimit) txOption.gasLimit = gasLimit if (maxFeePerGas) txOption.maxFeePerGas = maxFeePerGas if (maxPriorityFeePerGas) txOption.maxPriorityFeePerGas = maxPriorityFeePerGas const tx = await wallet.sendTransaction({ to: address, value: ethers.utils.parseEther(amount), ...txOption }) const txReceipt = await tx.wait() const returnItem = { transferFrom: wallet.address, transferTo: address, amount, transactionHash: tx.hash, transactionReceipt: txReceipt as any, link: `${networkExplorers[network]}/tx/${tx.hash}` } return returnNodeExecutionData(returnItem) } catch (error) { throw handleErrorMessage(error) } } } module.exports = { nodeClass: ETHTransfer } ================================================ FILE: packages/components/nodes/EmailSend/EmailSend.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { returnNodeExecutionData } from '../../src/utils' import { createTransport } from 'nodemailer' import SMTPTransport = require('nodemailer/lib/smtp-transport') class EmailSend implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Send Email' this.name = 'emailSend' this.icon = 'emailsend.svg' this.type = 'action' this.category = 'Communication' this.version = 1.0 this.description = 'Send email to single or multiple receipients' this.incoming = 1 this.outgoing = 1 this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Email Send Smtp', name: 'emailSendSmtp' } ], default: 'emailSendSmtp' } ] as INodeParams[] this.inputParameters = [ { label: 'From Email', name: 'fromEmail', type: 'string', default: '', description: 'Email address of the sender.' }, { label: 'To Email', name: 'toEmail', type: 'string', default: '', description: 'Email address of the recipient. Multiple emails can be comma-separated.', optional: true }, { label: 'CC Email', name: 'ccEmail', type: 'string', default: '', description: 'Email address of CC recipient. Multiple emails can be comma-separated.', optional: true }, { label: 'Subject', name: 'subject', type: 'string', default: '', description: 'Subject line of the email.' }, { label: 'Body - Plain Text', name: 'text', type: 'string', rows: 5, default: '', description: 'Plain text message of email.', optional: true }, { label: 'Body - HTML', name: 'html', type: 'string', rows: 5, default: '', description: 'HTML text message of email.', optional: true }, { label: 'Ignore SSL', name: 'ignoreSSL', type: 'boolean', default: false, description: 'Send email regardless of SSL validation.', optional: true } ] as INodeParams[] } async run(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters const credentials = nodeData.credentials if (inputParametersData === undefined) { throw new Error('Required data missing') } if (credentials === undefined) { throw new Error('Missing credentials') } const fromEmail = inputParametersData.fromEmail as string const toEmail = inputParametersData.toEmail as string const ccEmail = inputParametersData.ccEmail as string const subject = inputParametersData.subject as string const text = inputParametersData.text as string const html = inputParametersData.html as string const ignoreSSL = inputParametersData.ignoreSSL as boolean const connectionOptions: SMTPTransport.Options = { host: credentials.host as string, port: credentials.port as number, secure: credentials.secure as boolean } if (credentials.user || credentials.password) { connectionOptions.auth = { user: credentials.user as string, pass: credentials.password as string } } if (ignoreSSL) { connectionOptions.tls = { rejectUnauthorized: false } } const transporter = createTransport(connectionOptions) const mailOptions = { from: fromEmail, to: toEmail, cc: ccEmail, subject, text, html } const info = await transporter.sendMail(mailOptions) const returnData: ICommonObject[] = [] if (Array.isArray(info)) { returnData.push(...info) } else { returnData.push(info as unknown as ICommonObject) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: EmailSend } ================================================ FILE: packages/components/nodes/EmailTrigger/EmailTrigger.ts ================================================ import { INode, INodeData, INodeParams, IProviders, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import EventEmitter from 'events' import Imap from 'imap' import moment from 'moment' import { simpleParser } from 'mailparser' class EmailTrigger extends EventEmitter implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number credentials?: INodeParams[] providers: IProviders constructor() { super() this.label = 'Email Trigger' this.name = 'emailTrigger' this.icon = 'email-trigger.svg' this.type = 'trigger' this.category = 'Communication' this.version = 1.0 this.description = 'Start workflow whenever a new email is received' this.incoming = 0 this.outgoing = 1 this.providers = {} this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Imap', name: 'imap' } ], default: 'imap' } ] as INodeParams[] } async runTrigger(nodeData: INodeData): Promise { const credentials = nodeData.credentials if (credentials === undefined) { throw new Error('Imap credentials missing') } let timeNow = new Date().getTime() const imap = new Imap({ user: credentials.userEmail as string, password: credentials.password as string, host: credentials.host as string, port: credentials.port as number, tls: credentials.tls as boolean }) const openInbox = (cb: any) => { imap.openBox('INBOX', true, cb) } imap.once('ready', () => { openInbox((err: any) => { if (err) throw handleErrorMessage(err) imap.on('mail', () => { try { imap.search(['NEW', ['SINCE', moment().format('MMMM D, YYYY')]], (err, results) => { if (err) throw handleErrorMessage(err) if (!results || !results.length) { this.emit(emitEventKey, returnNodeExecutionData({ message: 'No new unread emails' })) } const f = imap.fetch(results, { bodies: '', markSeen: true }) f.on('message', (msg: Imap.ImapMessage) => { msg.on('body', (stream) => { simpleParser(stream, (err, mail) => { if (err) throw handleErrorMessage(err) const returnData = { from: mail.headers.get('from'), to: mail.headers.get('to'), subject: mail.subject, date: mail.date, text: mail.text, htmlText: mail.textAsHtml, html: mail.html } as any if (mail.headers.has('cc')) returnData.cc = mail.headers.get('cc') if (mail.headers.has('bcc')) returnData.bcc = mail.headers.get('bcc') // Convert Buffer to base64 string if (mail.attachments && mail.attachments.length) { for (let i = 0; i < mail.attachments.length; i += 1) { ;(mail.attachments[i] as any).content = `data:${ mail.attachments[i].contentType };base64,${mail.attachments[i].content.toString('base64')}` } returnData.attachments = mail.attachments } const emailDate = mail.date?.getTime() if (emailDate && timeNow < emailDate) { timeNow = emailDate this.emit(emitEventKey, returnNodeExecutionData(returnData)) } }) }) }) f.once('error', (err) => { console.error('on mail error: ', err) throw new Error('Email Trigger Error: ' + err) }) }) } catch (err) { console.error(err) throw new Error('Email Trigger Error: ' + err) } }) }) }) imap.once('error', (err: any) => { console.error('imap error = ', err) throw new Error('Email Trigger Error: ' + err) }) imap.connect() const emitEventKey = nodeData.emitEventKey as string this.providers[emitEventKey] = { provider: imap } } async removeTrigger(nodeData: INodeData): Promise { const emitEventKey = nodeData.emitEventKey as string if (Object.prototype.hasOwnProperty.call(this.providers, emitEventKey)) { const provider = this.providers[emitEventKey].provider if (provider) provider.end() this.removeAllListeners(emitEventKey) } } } module.exports = { nodeClass: EmailTrigger } ================================================ FILE: packages/components/nodes/Etherscan/Etherscan.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' import { NETWORK, NETWORK_LABEL, etherscanAPIs } from '../../src/ChainNetwork' import axios, { AxiosRequestConfig, Method } from 'axios' import { SORT_BY, OPERATIONS, GET_ETHER_BALANCE, GET_HISTORICAL_ETHER_BALANCE, GET_NORMAL_TRANSACTIONS, GET_INTERNAL_TRANSACTIONS, GET_INTERNAL_TRANSACTIONS_BY_HASH, GET_INTERNAL_TRANSACTIONS_BY_BLOCK, GET_BLOCKS_VALIDATED, GET_ABI, GET_CONTRACT_SOURCE_CODE, CHECK_TRANSACTION_RECEIPT_STATUS, GET_ERC20_TOKEN_SUPPLY, GET_ERC20_TOKEN_BALANCE, GET_HISTORICAL_ERC20_TOKEN_SUPPLY, GET_HISTORICAL_ERC20_TOKEN_BALANCE, GET_TOKEN_INFO, GET_ETHER_PRICE, GET_HISTORICAL_ETHER_PRICE, GET_MULTI_ETHER_BALANCE } from './constants' class Etherscan implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions: INodeParams[] credentials?: INodeParams[] networks?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Etherscan' this.name = 'etherscan' this.icon = 'etherscan.svg' this.type = 'action' this.category = 'Block Explorer' this.version = 1.0 this.description = 'Perform Etherscan operations' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'API', name: 'api', type: 'options', options: [ { label: 'Get Ether Balance for a Single Address', name: GET_ETHER_BALANCE.name, description: 'Returns the Ether balance of a given address.' }, { label: 'Get Ether Balance for Multiple Addresses(separated by a comma)', name: GET_MULTI_ETHER_BALANCE.name, description: 'Returns the Ether balance of the addresses(each address separated by a comma) entered.' }, { label: 'Get transactions', name: GET_NORMAL_TRANSACTIONS.name, description: 'Returns the list of transactions performed by an address, with optional pagination.' }, { label: 'Get internal transactions', name: GET_INTERNAL_TRANSACTIONS.name, description: 'Returns the list of internal transactions performed by an address, with optional pagination.' }, { label: 'Get internal transactions by hash', name: GET_INTERNAL_TRANSACTIONS_BY_HASH.name, description: 'Returns the list of internal transactions performed within a transaction.' }, { label: 'Get internal transactions by block', name: GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name, description: 'Returns the list of internal transactions performed within a block range, with optional pagination.' }, { label: 'Get list of Blocks Validated by Address', name: GET_BLOCKS_VALIDATED.name, description: 'Returns the list of blocks validated by an address.' }, { label: 'Get Contract ABI', name: GET_ABI.name, description: 'Returns the contract Application Binary Interface ( ABI ) of a verified smart contract.' }, { label: 'Get Contract Source Code', name: GET_CONTRACT_SOURCE_CODE.name, description: 'Returns the Solidity source code of a verified smart contract.' }, { label: 'Check Transaction Receipt Status', name: CHECK_TRANSACTION_RECEIPT_STATUS.name, description: 'Returns the status code of a transaction execution.' }, { label: 'Get ERC20 Token Supply', name: GET_ERC20_TOKEN_SUPPLY.name, description: `Returns the total supply of a ERC-20 token. The result is returned in the token's smallest decimal representation. Eg. a token with a balance of 215.241526476136819398 and 18 decimal places will be returned as 215241526476136819398` }, { label: 'Get ERC20 Token Balance', name: GET_ERC20_TOKEN_BALANCE.name, description: `Returns the current balance of a ERC-20 token of an address. The result is returned in the token's smallest decimal representation. Eg. a token with a balance of 215.241526476136819398 and 18 decimal places will be returned as 215241526476136819398` }, { label: 'Get Historical ERC-20 Token TotalSupply by ContractAddress & BlockNo [PRO]', name: GET_HISTORICAL_ERC20_TOKEN_SUPPLY.name, description: `Returns the historical amount of a ERC-20 token in circulation at a certain block height. The result is returned in the token's smallest decimal representation. Eg. a token with a balance of 215.241526476136819398 and 18 decimal places will be returned as 215241526476136819398` }, { label: 'Get Historical ERC-20 Token Account Balance by ContractAddress & BlockNo [PRO]', name: GET_HISTORICAL_ERC20_TOKEN_BALANCE.name, description: `Returns the balance of a ERC-20 token of an address at a certain block height. The result is returned in the token's smallest decimal representation. Eg. a token with a balance of 215.241526476136819398 and 18 decimal places will be returned as 215241526476136819398` }, { label: 'Get Token Info [PRO]', name: GET_TOKEN_INFO.name, description: 'Returns project information and social media links of an ERC-20/ERC-721 token.' }, { label: 'Get ETHER Price', name: GET_ETHER_PRICE.name, description: 'Returns the latest price of 1 ETHER.' }, { label: 'Get Historical ETHER Price [PRO]', name: GET_HISTORICAL_ETHER_PRICE.name, description: 'Returns the historical price of ETHER.' } ], default: 'getEtherBalance' } ] as INodeParams[] this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [ { label: NETWORK_LABEL.MAINNET, name: NETWORK.MAINNET }, { label: NETWORK_LABEL.GÖRLI, name: NETWORK.GÖRLI } ], default: 'homestead' } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Etherscan API Key', name: 'etherscanApi' } ], default: 'etherscanApi' } ] as INodeParams[] this.inputParameters = [ { label: 'Address', name: 'address', type: 'string', description: 'The address parameter(s) required', show: { 'actions.api': [ GET_ETHER_BALANCE.name, GET_HISTORICAL_ETHER_BALANCE.name, GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_BLOCKS_VALIDATED.name, GET_ABI.name, GET_CONTRACT_SOURCE_CODE.name, GET_ERC20_TOKEN_BALANCE.name, GET_HISTORICAL_ERC20_TOKEN_BALANCE.name ] } }, { label: 'Block Number', name: 'blockno', type: 'number', description: 'the block number to check balance for eg. 2000000', show: { 'actions.api': [ GET_HISTORICAL_ETHER_BALANCE.name, GET_HISTORICAL_ERC20_TOKEN_SUPPLY.name, GET_HISTORICAL_ERC20_TOKEN_BALANCE.name ] } }, { label: 'Start Block', name: 'startBlock', type: 'number', optional: true, description: 'the block number to start searching for transactions', show: { 'actions.api': [GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name] }, default: 0 }, { label: 'End Block', name: 'endBlock', type: 'number', optional: true, description: 'the block number to stop searching for transactions', show: { 'actions.api': [GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name] } }, { label: 'Page', name: 'page', type: 'number', optional: true, description: 'the page number, if pagination is enabled', show: { 'actions.api': [ GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name, GET_BLOCKS_VALIDATED.name ] }, default: 1 }, { label: 'Offset', name: 'offset', type: 'number', optional: true, description: 'the number of transactions displayed per page', show: { 'actions.api': [ GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name, GET_BLOCKS_VALIDATED.name ] }, default: 10 }, { label: 'Sort By', name: 'sortBy', type: 'options', optional: true, options: SORT_BY, show: { 'actions.api': [GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name] }, default: 'desc' }, { label: 'Transaction Hash', name: 'txhash', type: 'string', description: 'the string representing the transaction hash to check for internal transactions', show: { 'actions.api': [GET_INTERNAL_TRANSACTIONS_BY_HASH.name, CHECK_TRANSACTION_RECEIPT_STATUS.name] } }, { label: 'Block Type', name: 'blockType', type: 'options', options: [ { label: 'blocks', name: 'blocks' } ], default: 'blocks', show: { 'actions.api': [GET_BLOCKS_VALIDATED.name] } }, { label: 'Contract Address', name: 'contractAddress', type: 'string', description: 'the contract address of the ERC-20 token', show: { 'actions.api': [ GET_ERC20_TOKEN_SUPPLY.name, GET_ERC20_TOKEN_BALANCE.name, GET_HISTORICAL_ERC20_TOKEN_SUPPLY.name, GET_HISTORICAL_ERC20_TOKEN_BALANCE.name, GET_TOKEN_INFO.name ] } }, { label: 'Tag', name: 'tag', type: 'options', options: [{ label: 'latest', name: 'latest' }], default: 'latest', show: { 'actions.api': [GET_ERC20_TOKEN_BALANCE.name] } }, { label: 'Start Time', name: 'startTime', type: 'date', optional: true, show: { 'actions.api': [GET_HISTORICAL_ETHER_PRICE.name] } }, { label: 'End Time', name: 'endTime', type: 'date', optional: true, show: { 'actions.api': [GET_HISTORICAL_ETHER_PRICE.name] } } ] as INodeParams[] } getNetwork(network: NETWORK): string { return `${etherscanAPIs[network]}` } getBaseParams(api: string) { const operation = OPERATIONS.filter(({ name }) => name === api)[0] return { module: operation.module, action: operation.action } } getISODate(date: Date) { return date.toISOString().split('T')[0] } async run(nodeData: INodeData): Promise { const actionData = nodeData.actions const networksData = nodeData.networks const inputParameters = nodeData.inputParameters const credentials = nodeData.credentials if (actionData === undefined || inputParameters === undefined || credentials === undefined || networksData === undefined) { throw new Error('Required data missing') } // GET api const api = actionData.api as string // GET network const network = networksData.network as NETWORK // GET credentials const apiKey = credentials.apiKey as string // GET address const address = inputParameters.address as string const startblock = inputParameters.startBlock as number const endblock = inputParameters.endBlock as number const page = inputParameters.page as number const offset = inputParameters.offset as number const sort = inputParameters.sortBy as string const txhash = inputParameters.txhash as string const blocktype = inputParameters.blockType as string const contractaddress = inputParameters.contractAddress as string const tag = inputParameters.tag as string const startTime = inputParameters.startTime as string const endTime = inputParameters.endTime as string const startdate = startTime ? this.getISODate(new Date(startTime)) : undefined const enddate = endTime ? this.getISODate(new Date(endTime)) : undefined const url = this.getNetwork(network) const { module, action } = this.getBaseParams(api) const queryParameters = { module, action, address, apiKey, startblock, endblock, page, offset, sort, txhash, blocktype, contractaddress, tag, startdate, enddate } const returnData: ICommonObject[] = [] let responseData: any try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url, params: queryParameters, paramsSerializer: (params) => serializeQueryParams(params), headers: { 'Content-Type': 'application/json' } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: Etherscan } ================================================ FILE: packages/components/nodes/Etherscan/constants.ts ================================================ // Account export const GET_ETHER_BALANCE = { name: 'getEtherBalance', module: 'account', action: 'balance' } export const GET_MULTI_ETHER_BALANCE = { name: 'getEtherBalanceMulti', module: 'account', action: 'balancemulti' } export const GET_HISTORICAL_ETHER_BALANCE = { name: 'getHistoricalEtherBalance', module: 'account', action: 'balancehistory' } export const GET_NORMAL_TRANSACTIONS = { name: 'getTransactions', module: 'account', action: 'txlist' } export const GET_INTERNAL_TRANSACTIONS = { name: 'getInternalTransactions', module: 'account', action: 'txlistinternal' } export const GET_INTERNAL_TRANSACTIONS_BY_HASH = { name: 'getInternalTransactionsByHash', module: 'account', action: 'txlistinternal' } export const GET_INTERNAL_TRANSACTIONS_BY_BLOCK = { name: 'getInternalTransactionsByBlock', module: 'account', action: 'txlistinternal' } export const GET_BLOCKS_VALIDATED = { name: 'getBlocksValidated', module: 'account', action: 'getminedblocks' } // Contracts export const GET_ABI = { name: 'getAbi', module: 'contract', action: 'getabi' } export const GET_CONTRACT_SOURCE_CODE = { name: 'getContractSourceCode', module: 'contract', action: 'getsourcecode' } // Transactions export const CHECK_TRANSACTION_RECEIPT_STATUS = { name: 'getTransactionReceiptStatus', module: 'transaction', action: 'gettxreceiptstatus' } // Tokens export const GET_ERC20_TOKEN_SUPPLY = { name: 'getErc20TokenSupply', module: 'stats', action: 'tokensupply' } export const GET_ERC20_TOKEN_BALANCE = { name: 'getErc20TokenBalance', module: 'account', action: 'tokenbalance' } export const GET_HISTORICAL_ERC20_TOKEN_SUPPLY = { name: 'getHistoricalErc20TokenSupply', module: 'stats', action: 'tokensupplyhistory' } export const GET_HISTORICAL_ERC20_TOKEN_BALANCE = { name: 'getHistoricalErc20TokenBalance', module: 'account', action: 'tokenbalancehistory' } export const GET_TOKEN_INFO = { name: 'getTokenInfo', module: 'token', action: 'tokeninfo' } // Stats export const GET_ETHER_PRICE = { name: 'getEtherPrice', module: 'stats', action: 'ethprice' } export const GET_HISTORICAL_ETHER_PRICE = { name: 'getHistoricalEtherPrice', module: 'stats', action: 'ethdailyprice' } export const OPERATIONS = [ GET_ETHER_BALANCE, GET_MULTI_ETHER_BALANCE, GET_HISTORICAL_ETHER_BALANCE, GET_NORMAL_TRANSACTIONS, GET_INTERNAL_TRANSACTIONS, GET_INTERNAL_TRANSACTIONS_BY_HASH, GET_INTERNAL_TRANSACTIONS_BY_BLOCK, GET_BLOCKS_VALIDATED, GET_ABI, GET_CONTRACT_SOURCE_CODE, CHECK_TRANSACTION_RECEIPT_STATUS, GET_ERC20_TOKEN_SUPPLY, GET_ERC20_TOKEN_BALANCE, GET_HISTORICAL_ERC20_TOKEN_SUPPLY, GET_HISTORICAL_ERC20_TOKEN_BALANCE, GET_TOKEN_INFO, GET_ETHER_PRICE, GET_HISTORICAL_ETHER_PRICE ] as const export const SORT_BY = [ { label: 'Desc', name: 'desc' }, { label: 'Asc', name: 'asc' } ] ================================================ FILE: packages/components/nodes/ExecuteContractFunction/ExecuteContractFunction.ts ================================================ import { ethers } from 'ethers' import { IContract, IDbCollection, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, IWallet, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import { networkExplorers, getNetworkProvidersList, NETWORK_PROVIDER, getNetworkProvider, NETWORK, networkProviderCredentials } from '../../src/ChainNetwork' class ExecuteContractFunction implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] actions?: INodeParams[] constructor() { this.label = 'Execute Contract Function' this.name = 'executeContractFunction' this.icon = 'execute-contract-function.svg' this.type = 'action' this.category = 'Smart Contract' this.version = 1.1 this.description = 'Execute smart contract function.' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Select Contract', name: 'contract', type: 'asyncOptions', loadFromDbCollections: ['Contract'], loadMethod: 'getContracts' }, { label: 'Function', name: 'function', type: 'asyncOptions', loadMethod: 'getFunctions' }, { label: 'Function Parameters', name: 'funcParameters', type: 'json', placeholder: '["param1", "param2"]', description: 'Function parameters in array. Ex: ["param1", "param2"]', optional: true }, { label: 'Select Wallet', name: 'wallet', type: 'asyncOptions', description: 'Connect wallet to sign transactions for functions that require changing states on blockchain, i.e: nonpayable or payable.', loadFromDbCollections: ['Wallet'], loadMethod: 'getWallets', show: { 'actions.function': '(\\(payable\\)|\\(nonpayable\\))' } } ] as INodeParams[] this.networks = [ { label: 'Network Provider', name: 'networkProvider', type: 'asyncOptions', loadMethod: 'getNetworkProviders' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] this.inputParameters = [ { label: 'Gas Limit', name: 'gasLimit', type: 'number', optional: true, placeholder: '100000', description: 'Maximum price you are willing to pay when sending a transaction' }, { label: 'Max Fee per Gas', name: 'maxFeePerGas', type: 'number', optional: true, placeholder: '200', description: 'The maximum price (in wei) per unit of gas for transaction. See more' }, { label: 'Max Priority Fee per Gas', name: 'maxPriorityFeePerGas', type: 'number', optional: true, placeholder: '5', description: 'The priority fee price (in wei) per unit of gas for transaction. See more' } ] as INodeParams[] } loadMethods = { async getContracts(nodeData: INodeData, dbCollection?: IDbCollection): Promise { const returnData: INodeOptionsValue[] = [] if (dbCollection === undefined || !dbCollection || !dbCollection.Contract) { return returnData } const contracts: IContract[] = dbCollection.Contract for (let i = 0; i < contracts.length; i += 1) { const contract = contracts[i] const data = { label: `${contract.name} (${contract.network})`, name: JSON.stringify(contract), description: contract.address } as INodeOptionsValue returnData.push(data) } return returnData }, async getFunctions(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const actionsData = nodeData.actions if (actionsData === undefined) { return returnData } const contractString = (actionsData.contract as string) || '' if (!contractString) return returnData try { const contractDetails = JSON.parse(contractString) if (!contractDetails.abi || !contractDetails.address) return returnData const abiString = contractDetails.abi const abi = JSON.parse(abiString) for (const item of abi) { if (!item.name) continue if (item.type === 'function') { const funcName = `${item.name} (${item.stateMutability})` const funcInputs = item.inputs let inputParameters = '' let inputTypes = '' for (let i = 0; i < funcInputs.length; i++) { const input = funcInputs[i] inputTypes += `${input.type} ${input.name}` if (i !== funcInputs.length - 1) inputTypes += ', ' inputParameters += `
  • ${input.type} ${input.name}
  • ` } if (inputParameters) { inputParameters = '
      ' + inputParameters + '
    ' } else { inputParameters = '
      ' + 'none' + '
    ' } returnData.push({ label: funcName, name: funcName, description: inputTypes, inputParameters }) } } return returnData } catch (e) { return returnData } }, async getWallets(nodeData: INodeData, dbCollection?: IDbCollection): Promise { const returnData: INodeOptionsValue[] = [] const actionsData = nodeData.actions if (actionsData === undefined) { return returnData } const contractString = (actionsData.contract as string) || '' if (!contractString) return returnData try { const contractDetails = JSON.parse(contractString) if (!contractDetails.network) return returnData if (dbCollection === undefined || !dbCollection || !dbCollection.Wallet) { return returnData } const wallets: IWallet[] = dbCollection.Wallet for (let i = 0; i < wallets.length; i += 1) { const wallet = wallets[i] const data = { label: `${wallet.name} (${wallet.network})`, name: JSON.stringify(wallet), description: wallet.address } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } }, async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const actionData = nodeData.actions if (actionData === undefined) { return returnData } const contractString = (actionData.contract as string) || '' if (!contractString) return returnData try { const contractDetails = JSON.parse(contractString) if (!contractDetails.network) return returnData const network = contractDetails.network return getNetworkProvidersList(network) } catch (e) { return returnData } } } async run(nodeData: INodeData): Promise { const networksData = nodeData.networks const actionsData = nodeData.actions const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (networksData === undefined || actionsData === undefined) { throw new Error('Required data missing') } try { const contractString = (actionsData.contract as string) || '' const contractDetails: IContract = JSON.parse(contractString) const network = contractDetails.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') // Get contract details const abiString = contractDetails.abi const address = contractDetails.address const abi = JSON.parse(abiString) let functionName = (actionsData.function as string) || '' let contractParameters: any[] = [] const funcParameters = actionsData.funcParameters as string if (funcParameters) { try { contractParameters = JSON.parse(funcParameters.replace(/\s/g, '')) } catch (error) { throw handleErrorMessage(error) } } let contractInstance: ethers.Contract if (new RegExp('(\\(payable\\)|\\(nonpayable\\))').test(functionName)) { // Get wallet instance const walletString = actionsData.wallet as string const walletDetails: IWallet = JSON.parse(walletString) const walletCredential = JSON.parse(walletDetails.walletCredential) const wallet = new ethers.Wallet(walletCredential.privateKey as string, provider) contractInstance = new ethers.Contract(address, abi, wallet) const gasLimit = (inputParametersData?.gasLimit as number) || 3000000 const maxFeePerGas = (inputParametersData?.maxFeePerGas as number) || undefined const maxPriorityFeePerGas = (inputParametersData?.maxPriorityFeePerGas as number) || undefined const gasPrice = await provider.getGasPrice() const nonce = await provider.getTransactionCount(walletDetails.address) const txOption = { gasPrice, gasLimit, nonce } as any if (maxFeePerGas) txOption.maxFeePerGas = maxFeePerGas if (maxPriorityFeePerGas) txOption.maxPriorityFeePerGas = maxPriorityFeePerGas functionName = functionName.split(' ')[0] const tx = await contractInstance[functionName].apply(null, contractParameters.length ? contractParameters : null, txOption) const approveReceipt = await tx.wait() if (approveReceipt.status === 0) throw new Error(`Function ${functionName} failed to send transaction`) const returnItem = { function: functionName, transactionHash: tx.hash, transactionReceipt: approveReceipt, link: `${networkExplorers[network]}/tx/${tx.hash}` } return returnNodeExecutionData(returnItem) } else { contractInstance = new ethers.Contract(address, abi, provider) functionName = functionName.split(' ')[0] const result = await contractInstance[functionName].apply(null, contractParameters.length ? contractParameters : null) const returnItem = { function: functionName, result: result } return returnNodeExecutionData(returnItem) } } catch (e) { throw handleErrorMessage(e) } } } module.exports = { nodeClass: ExecuteContractFunction } ================================================ FILE: packages/components/nodes/FlowBalanceTrigger/FlowBalanceTrigger.ts ================================================ import { CronJob } from 'cron' import { BigNumber, BigNumberish } from 'ethers' import { ICronJobs, INode, INodeData, INodeOptionsValue, INodeParams, NodeType } from '../../src/Interface' import { returnNodeExecutionData } from '../../src/utils' import EventEmitter from 'events' import axios from 'axios' import { FLOWNetworks, getNetworkProvidersList, NETWORK, networkExplorers } from '../../src/ChainNetwork' import { formatUnits, parseUnits } from 'ethers/lib/utils' class FlowBalanceTrigger extends EventEmitter implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] cronJobs: ICronJobs constructor() { super() this.label = 'Flow Balance Trigger' this.name = 'FlowBalanceTrigger' this.icon = 'flow.png' this.type = 'trigger' this.category = 'Cryptocurrency' this.version = 1.0 this.description = 'Start workflow whenever FLOW balance in wallet changes' this.incoming = 0 this.outgoing = 1 this.cronJobs = {} this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...FLOWNetworks], default: 'homestead' } ] as INodeParams[] this.inputParameters = [ { label: 'Wallet Address', name: 'address', type: 'string', default: '' }, { label: 'Trigger Condition', name: 'triggerCondition', type: 'options', options: [ { label: 'When balance increased', name: 'increase' }, { label: 'When balance decreased', name: 'decrease' } ], default: 'increase' }, { label: 'Polling Time', name: 'pollTime', type: 'options', options: [ { label: 'Every 15 secs', name: '15s' }, { label: 'Every 30 secs', name: '30s' }, { label: 'Every 1 min', name: '1min' }, { label: 'Every 5 min', name: '5min' }, { label: 'Every 10 min', name: '10min' } ], default: '30s' } ] as INodeParams[] } loadMethods = { async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK return getNetworkProvidersList(network) } } async runTrigger(nodeData: INodeData): Promise { const networksData = nodeData.networks const inputParametersData = nodeData.inputParameters if (networksData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } const network = networksData.network as NETWORK const baseUrl = network === NETWORK.FLOW_TESTNET ? `https://rest-testnet.onflow.org/v1/accounts/` : `https://rest-mainnet.onflow.org/v1/accounts/` const emitEventKey = nodeData.emitEventKey as string const address = (inputParametersData.address as string) || '' const pollTime = (inputParametersData.pollTime as string) || '30s' const triggerCondition = (inputParametersData.triggerCondition as string) || 'increase' const cronTimes: string[] = [] if (pollTime === '15s') { cronTimes.push(`*/15 * * * * *`) } else if (pollTime === '30s') { cronTimes.push(`*/30 * * * * *`) } else if (pollTime === '1min') { cronTimes.push(`*/1 * * * *`) } else if (pollTime === '5min') { cronTimes.push(`*/5 * * * *`) } else if (pollTime === '10min') { cronTimes.push(`*/10 * * * *`) } const responseFlow: any = await axios.get(`${baseUrl}${address}`) let lastBalance: BigNumber = BigNumber.from(responseFlow.data.balance) const executeTrigger = async () => { const responseFlow: any = await axios.get(`${baseUrl}${address}`) const newBalance: BigNumber = BigNumber.from(responseFlow.data.balance) if (!newBalance.eq(lastBalance)) { if (triggerCondition === 'increase' && newBalance.gt(lastBalance)) { const balanceInFlow = this.formatFlow(BigNumber.from(newBalance.toString())) const diffInFlow = newBalance.sub(lastBalance) const returnItem = { newBalance: `${balanceInFlow} FLOW`, lastBalance: `${this.formatFlow(BigNumber.from(lastBalance.toString()))} FLOW`, difference: `${this.formatFlow(BigNumber.from(diffInFlow.toString()))} FLOW`, explorerLink: `${networkExplorers[network]}/address/${address}`, triggerCondition: 'FLOW balance increase' } lastBalance = newBalance this.emit(emitEventKey, returnNodeExecutionData(returnItem)) } else if (triggerCondition === 'decrease' && newBalance.lt(lastBalance)) { const balanceInFlow = this.formatFlow(BigNumber.from(newBalance.toString())) const diffInFlow = lastBalance.sub(newBalance) const returnItem = { newBalance: `${balanceInFlow} FLOW`, lastBalance: `${this.formatFlow(BigNumber.from(lastBalance.toString()))} FLOW`, difference: `${this.formatFlow(BigNumber.from(diffInFlow.toString()))} FLOW`, explorerLink: `${networkExplorers[network]}/address/${address}`, triggerCondition: 'FLOW balance decrease' } lastBalance = newBalance this.emit(emitEventKey, returnNodeExecutionData(returnItem)) } else { lastBalance = newBalance } } } // Start the cron-jobs if (Object.prototype.hasOwnProperty.call(this.cronJobs, emitEventKey)) { for (const cronTime of cronTimes) { // Automatically start the cron job this.cronJobs[emitEventKey].push(new CronJob(cronTime, executeTrigger, undefined, true)) } } else { for (const cronTime of cronTimes) { // Automatically start the cron job this.cronJobs[emitEventKey] = [new CronJob(cronTime, executeTrigger, undefined, true)] } } } async removeTrigger(nodeData: INodeData): Promise { const emitEventKey = nodeData.emitEventKey as string if (Object.prototype.hasOwnProperty.call(this.cronJobs, emitEventKey)) { const cronJobs = this.cronJobs[emitEventKey] for (const cronJob of cronJobs) { cronJob.stop() } this.removeAllListeners(emitEventKey) } } formatFlow(balance: BigNumberish): string { return formatUnits(balance, 8) } parseFlow(flow: string): BigNumber { return parseUnits(flow, 8) } } module.exports = { nodeClass: FlowBalanceTrigger } ================================================ FILE: packages/components/nodes/FootprintAnalytics/FootprintAnalytics.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' import { ALL_OPERATIONS, chainInputParameters, CHAIN_OPERATIONS, gamefiParams, GAMEFI_OPERATIONS, limitOffetParams, nftBalanceParams, nftParams, nftWashTradeCheckerParams, NFT_OPERATIONS, timeRangeParams, tokenBalanceParams, tokenParams, TOKEN_OPERATIONS } from './constants' class FootprintAnalytics implements INode { // properties label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number // parameter actions: INodeParams[] credentials?: INodeParams[] networks?: INodeParams[] inputParameters?: INodeParams[] constructor() { // properties this.label = 'Footprint Analytics' this.name = 'footprintAnalytics' this.icon = 'footprint-analytics.png' this.type = 'action' this.category = 'Block Explorer' this.version = 1.0 this.description = 'Execute Footprint Analytics APIs and SQL query' this.incoming = 1 this.outgoing = 1 // parameter this.actions = [ { label: 'API', name: 'api', type: 'options', options: [ { label: 'Rest API (V2)', name: 'restAPI' }, { label: 'SQL API (Synchronous)', name: 'sqlAPISynchronous', description: 'Suitable for simple table lookup query' }, { label: 'SQL API (Asynchronous)', name: 'sqlAPIAsynchronous', description: 'For complex analysis table lookup query' } ] }, { label: 'Category', name: 'category', type: 'options', options: [ { label: 'NFT', name: 'nft' }, { label: 'Token', name: 'token' }, { label: 'GameFi', name: 'gamefi' }, { label: 'Chain', name: 'chain' } ], show: { 'actions.api': ['restAPI'] } }, { label: 'Operation', name: 'operation', type: 'asyncOptions', loadMethod: 'getOperations', show: { 'actions.api': ['restAPI'] } } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Footprint Analytics API Key', name: 'footprintAnalyticsApi' } ], default: 'footprintAnalyticsApi' } ] as INodeParams[] this.inputParameters = [ ...chainInputParameters, ...nftParams, ...nftWashTradeCheckerParams, ...nftBalanceParams, ...tokenParams, ...tokenBalanceParams, ...gamefiParams, ...timeRangeParams, ...limitOffetParams, { label: 'SQL query', name: 'sqlQuery', type: 'string', rows: 5, placeholder: `select * from ethereum_token_transfers where block_timestamp >= date_add('day',-1,current_date) limit 10`, description: 'SQL query to execute. Must be Scale or Enterprise plan', show: { 'actions.api': ['sqlAPISynchronous', 'sqlAPIAsynchronous'] } } ] } getFormattedDate(date: Date, operation: string) { const hours = date.toISOString().split('T')[1].split(':')[0] const minutes = date.toISOString().split('T')[1].split(':')[1] const seconds = date.toISOString().split('T')[1].split(':')[2] return operation === 'nftCollectionStatistics' ? date.getTime() : `${date.toISOString().split('T')[0]} ${hours}:${minutes}:${seconds}` } loadMethods = { async getOperations(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const actionsData = nodeData.actions if (actionsData === undefined) { return returnData } const category = actionsData.category as string let operations: any[] = [] switch (category) { case 'nft': operations = NFT_OPERATIONS break case 'token': operations = TOKEN_OPERATIONS break case 'gamefi': operations = GAMEFI_OPERATIONS break case 'chain': operations = CHAIN_OPERATIONS break } for (const op of operations) { returnData.push({ label: op.label, name: op.name, description: op.description }) } return returnData } } async run(nodeData: INodeData): Promise { const { actions, inputParameters, credentials } = nodeData if (actions === undefined || inputParameters === undefined || credentials === undefined) { throw new Error('Required data missing') } const api = actions.api as string const apiKey = credentials.apiKey as string const operation = actions.operation as string const chain = inputParameters.chain as string const collection_contract_address = inputParameters.collection_contract_address as string const type = inputParameters.type as string const status = inputParameters.status as string const statistics_metrics = inputParameters.statistics_metrics as string const statistics_time_model = inputParameters.statistics_time_model as string const wallet_address = inputParameters.wallet_address as string const nft_type = inputParameters.nft_type as string const nft_token_id = inputParameters.nft_token_id as string const token_address = inputParameters.token_address as string const from_address = inputParameters.from_address as string const to_address = inputParameters.to_address as string const protocol_slug = inputParameters.protocol_slug as string const address_type = inputParameters.address_type as string const statistics_frequency_model = inputParameters.statistics_frequency_model as string const contract_address = inputParameters.contract_address as string const startTime = inputParameters.start_time as string const endTime = inputParameters.end_time as string const offset = inputParameters.offset as number const limit = inputParameters.limit as number const start_time = startTime ? this.getFormattedDate(new Date(startTime), operation) : undefined const end_time = endTime ? this.getFormattedDate(new Date(endTime), operation) : undefined const sqlQuery = inputParameters.sqlQuery as string const queryParameters = { chain, collection_contract_address, type, status, statistics_metrics, statistics_time_model, nft_type, protocol_slug, address_type } as any if (nft_token_id) queryParameters.nft_token_id = nft_token_id if (start_time) queryParameters.start_time = start_time if (end_time) queryParameters.end_time = end_time if (wallet_address) queryParameters.wallet_address = wallet_address if (token_address) queryParameters.token_address = token_address if (from_address) queryParameters.from_address = from_address if (to_address) queryParameters.to_address = to_address if (statistics_frequency_model) queryParameters.statistics_frequency_model = statistics_frequency_model if (contract_address) queryParameters.contract_address = contract_address if (offset) queryParameters.offset = offset if (limit) queryParameters.limit = limit const returnData: ICommonObject[] = [] let responseData: any if (api === 'sqlAPISynchronous') { try { const axiosConfig: AxiosRequestConfig = { method: 'POST' as Method, url: 'https://api.footprint.network/api/v1/native', data: JSON.stringify({ query: sqlQuery }), headers: { 'Content-Type': 'application/json', 'API-KEY': apiKey } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { console.error(error) throw handleErrorMessage(error) } } else if (api === 'sqlAPIAsynchronous') { try { const axiosConfig: AxiosRequestConfig = { method: 'POST' as Method, url: 'https://api.footprint.network/api/v1/native/async', data: JSON.stringify({ query: sqlQuery }), headers: { 'Content-Type': 'application/json', 'API-KEY': apiKey } } const response = await axios(axiosConfig) responseData = response.data const promise = (execution_id: string) => { let data: ICommonObject = {} return new Promise((resolve, reject) => { const timeout = setInterval(async () => { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://api.footprint.network/api/v1/native/${execution_id}/results`, headers: { 'Content-Type': 'application/json', 'API-KEY': apiKey } } const response = await axios(axiosConfig) data = response.data const state = response.data.data.state as 'INIT' | 'PENDING' | 'SUCCESS' | 'FAIL' | 'EXPIRED' if (state === 'SUCCESS') { clearInterval(timeout) resolve(data) } else if (state === 'FAIL' || state === 'EXPIRED') { clearInterval(timeout) reject(new Error(`Error querying async SQL request: ${state}`)) } }, 1500) }) } responseData = await promise(responseData.data.execution_id || '') } catch (error) { console.error(error) throw handleErrorMessage(error) } } else if (api === 'restAPI') { try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: ALL_OPERATIONS.find((op) => op.name === operation)?.endpoint, params: queryParameters, paramsSerializer: (params) => serializeQueryParams(params), headers: { 'Content-Type': 'application/json', 'API-KEY': apiKey } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { console.error(error) throw handleErrorMessage(error) } } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: FootprintAnalytics } ================================================ FILE: packages/components/nodes/FootprintAnalytics/constants.ts ================================================ import { INodeParams } from '../../src' export const chainInputParameters = [ { label: 'Chain', name: 'chain', type: 'options', options: [ { label: 'All', name: 'all', show: { 'actions.operation': ['protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'protocolsByChain'] } }, { label: 'Arbitrum', name: 'Arbitrum', show: { 'actions.operation': [ 'tokenTransfers', 'tokenBalance', 'nftCollectionsByChain', 'nftBalance', 'protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'protocolsByChain', 'chainTransactions' ] } }, { label: 'Avalanche', name: 'Avalanche', show: { 'actions.operation': [ 'tokenTransfers', 'tokenBalance', 'nftCollectionsByChain', 'nftBalance', 'protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'protocolsByChain', 'chainTransactions' ] } }, { label: 'BNB Chain', name: 'BNB%20Chain', show: { 'actions.operation': [ 'tokenTransfers', 'tokenBalance', 'nftTransactions', 'nftTransfers', 'nftCollectionsByChain', 'nftBalance', 'protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'protocolsByChain', 'chainTransactions' ] } }, { label: 'Boba', name: 'Boba', show: { 'actions.operation': [ 'tokenTransfers', 'tokenBalance', 'nftBalance', 'protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'protocolsByChain', 'chainTransactions' ] } }, { label: 'Bitcoin', name: 'Bitcoin', show: { 'actions.operation': ['chainTransactions'] } }, { label: 'Cardano', name: 'Cardano', show: { 'actions.operation': ['nftCollectionsByChain'] } }, { label: 'Celo', name: 'Celo', show: { 'actions.operation': [ 'tokenTransfers', 'tokenBalance', 'nftBalance', 'protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'protocolsByChain', 'chainTransactions' ] } }, { label: 'DFK', name: 'DFK', show: { 'actions.operation': [ 'tokenTransfers', 'tokenBalance', 'nftBalance', 'protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'protocolsByChain', 'chainTransactions' ] } }, { label: 'Doge', name: 'Doge', show: { 'actions.operation': ['chainTransactions'] } }, { label: 'EOS', name: 'EOS', show: { 'actions.operation': ['tokenTransfers', 'chainTransactions'] } }, { label: 'Ethereum', name: 'Ethereum', show: { 'actions.operation': [ 'tokenTransfers', 'tokenBalance', 'allTokenBalance', 'nftTransactions', 'nftTransfers', 'nftOrders', 'nftWashTradeChecker', 'nftCollectionStatistics', 'nftStatistics', 'nftInfo', 'nftAttributes', 'nftCollectionsByChain', 'nftBalance', 'nftBalanceByWallet', 'protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'protocolsByChain', 'chainTransactions' ] } }, { label: 'Fantom', name: 'Fantom', show: { 'actions.operation': [ 'tokenTransfers', 'tokenBalance', 'nftCollectionsByChain', 'nftBalance', 'protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'protocolsByChain', 'chainTransactions' ] } }, { label: 'Harmony', name: 'Harmony', show: { 'actions.operation': [ 'tokenTransfers', 'tokenBalance', 'nftCollectionsByChain', 'nftBalance', 'protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'protocolsByChain', 'chainTransactions' ] } }, { label: 'Hive', name: 'Hive', show: { 'actions.operation': [ 'protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'protocolsByChain', 'chainTransactions' ] } }, { label: 'Heco', name: 'Heco', show: { 'actions.operation': ['tokenTransfers', 'tokenBalance', 'nftBalance', 'chainTransactions'] } }, { label: 'ImmutableX', name: 'ImmutableX', show: { 'actions.operation': ['nftCollectionsByChain'] } }, { label: 'IoTeX', name: 'IoTeX', show: { 'actions.operation': [ 'protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'protocolsByChain', 'chainTransactions' ] } }, { label: 'Moonbeam', name: 'Moonbeam', show: { 'actions.operation': [ 'tokenTransfers', 'tokenBalance', 'nftBalance', 'protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'protocolsByChain', 'chainTransactions' ] } }, { label: 'Moonriver', name: 'Moonriver', show: { 'actions.operation': [ 'tokenTransfers', 'tokenBalance', 'nftBalance', 'protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'protocolsByChain', 'chainTransactions' ] } }, { label: 'Oasys', name: 'Oasys', show: { 'actions.operation': ['tokenTransfers', 'tokenBalance', 'nftBalance', 'chainTransactions'] } }, { label: 'Optimism', name: 'Optimism', show: { 'actions.operation': ['tokenTransfers', 'tokenBalance', 'nftBalance', 'chainTransactions'] } }, { label: 'Polygon', name: 'Polygon', show: { 'actions.operation': [ 'tokenTransfers', 'tokenBalance', 'nftTransactions', 'nftTransfers', 'nftCollectionStatistics', 'nftBalance', 'protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'protocolsByChain', 'chainTransactions' ] } }, { label: 'Solana', name: 'Solana', show: { 'actions.operation': [ 'tokenTransfers', 'nftTransactions', 'nftTransfers', 'nftCollectionStatistics', 'nftStatistics', 'nftInfo', 'nftAttributes', 'nftCollectionsByChain', 'chainTransactions' ] } }, { label: 'ThunderCore', name: 'ThunderCore', show: { 'actions.operation': [ 'tokenTransfers', 'tokenBalance', 'nftBalance', 'protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'protocolsByChain', 'chainTransactions' ] } }, { label: 'Terra', name: 'Terra', show: { 'actions.operation': ['nftCollectionsByChain'] } }, { label: 'Wax', name: 'Wax', show: { 'actions.operation': ['tokenTransfers', 'chainTransactions'] } } ], show: { 'actions.api': ['restAPI'] }, hide: { 'actions.operation': ['chainInfo'] } } ] as INodeParams[] // NFT export const NFT_OPERATIONS = [ { label: 'NFT Transactions', name: 'nftTransactions', description: 'Returns the sales record of the NFT collection in the marketplace.', endpoint: 'https://api.footprint.network/api/v2/nft/collection/transactions' }, { label: 'NFT Transfers', name: 'nftTransfers', description: 'Returns the transfers record of the NFT collection.', endpoint: 'https://api.footprint.network/api/v2/nft/collection/transfers' }, { label: 'NFT Orders', name: 'nftOrders', description: 'Returns the sales listing of the NFT in the marketplace.', endpoint: 'https://api.footprint.network/api/v2/nft/collection/orderbook' }, { label: 'NFT Wash Trade Checker', name: 'nftWashTradeChecker', description: 'Returns whether the transaction hash is wash trading.', endpoint: 'https://api.footprint.network/api/v2/nft/collection/transactions/is-washtrade' }, { label: 'NFT Collection Statistics', name: 'nftCollectionStatistics', description: 'Returns the statistics metrics (eg:market_cap,volume,floor_price) timeseries data or all metrics data of the NFT collection.', endpoint: 'https://api.footprint.network/api/v2/nft/collection/statistics' }, { label: 'NFT Statistics', name: 'nftStatistics', description: 'Returns all metrics of NFT different time models statistics by collection and nft_token_id.Currently only the latest state of NFT statistics.', endpoint: 'https://api.footprint.network/api/v2/nft/statistics' }, { label: 'NFT Info', name: 'nftInfo', description: 'Returns the basic information of the NFT.', endpoint: 'https://api.footprint.network/api/v2/nft/info' }, { label: 'NFT Attributes', name: 'nftAttributes', description: 'Returns the attributes of the NFT.', endpoint: 'https://api.footprint.network/api/v2/nft/attributes' }, { label: 'NFT Collections by Chain', name: 'nftCollectionsByChain', description: 'Returns all NFT collections by chain.', endpoint: 'https://api.footprint.network/api/v2/nft/collection/info' }, { label: 'NFT Balance', name: 'nftBalance', description: 'Returns the NFT balance of the wallet address.', endpoint: 'https://api.footprint.network/api/v2/nft/balance' }, { label: 'NFT Balance by Wallet', name: 'nftBalanceByWallet', description: 'Get the list of NFTs balance by the specified wallet', endpoint: 'https://api.footprint.network/api/v2/wallet/nfts' } ] export const TOKEN_OPERATIONS = [ { label: 'Token Transfers', name: 'tokenTransfers', description: 'Returns token transfers over a period of time.', endpoint: 'https://api.footprint.network/api/v2/token/transfers' }, { label: 'Token Balance', name: 'tokenBalance', description: 'Returns a token balance for the specified wallet (ERC20)', endpoint: 'https://api.footprint.network/api/v2/token/balance' }, { label: 'All Token Balance', name: 'allTokenBalance', description: 'Retrieve all token balances for the specified wallet (ERC20)', endpoint: 'https://api.footprint.network/api/v2/wallet/tokens' } ] export const GAMEFI_OPERATIONS = [ { label: 'Protocol Transactions', name: 'protocolTransactions', description: 'Returns protocol transactions over a period of time.', endpoint: 'https://api.footprint.network/api/v2/protocol/transactions' }, { label: 'Protocol Addresses', name: 'protocolAddresses', description: 'Returns protocol addresses over a period of time.', endpoint: 'https://api.footprint.network/api/v2/protocol/address' }, { label: 'Protocol User Statistics', name: 'protocolUserStatistics', description: 'Returns protocol user statistics over a period of time.', endpoint: 'https://api.footprint.network/api/v2/protocol/statistics' }, { label: 'Protocols by Chain', name: 'protocolsByChain', description: 'Returns protocols by chain.', endpoint: 'https://api.footprint.network/api/v2/protocol/info' } ] export const CHAIN_OPERATIONS = [ { label: 'Chain Transactions', name: 'chainTransactions', description: 'Returns chain transactions over a period of time.', endpoint: 'https://api.footprint.network/api/v2/chain/transactions' }, { label: 'Chain Info', name: 'chainInfo', description: 'Returns chains info', endpoint: 'https://api.footprint.network/api/v2/chain/info' } ] export const ALL_OPERATIONS = [...NFT_OPERATIONS, ...TOKEN_OPERATIONS, ...GAMEFI_OPERATIONS, ...CHAIN_OPERATIONS] export const nftWashTradeCheckerParams = [ { label: 'Transaction Hash', name: 'transaction_hash', type: 'string', placeholder: '0x03a7615cd2bbe69a028debf6d76b7be99ba5c4d9246a639226889c5618a648fe', show: { 'actions.operation': ['nftWashTradeChecker'] } } ] as INodeParams[] export const nftBalanceParams = [ { label: 'Wallet Address', name: 'wallet_address', type: 'string', placeholder: '0x3cd3a9a9c66a6fbb7221a30e761bda3caaea197b', show: { 'actions.operation': ['nftBalance', 'nftBalanceByWallet'] } }, { label: 'NFT Token Id', name: 'nft_token_id', type: 'string', placeholder: '7237005577332285910032237647762877720934446650543256038857901852224856784896', show: { 'actions.operation': ['nftBalance'] } }, { label: 'NFT Type', name: 'nft_type', type: 'options', options: [ { label: 'ERC1155', name: 'ERC1155' }, { label: 'ERC721', name: 'ERC721' } ], default: 'ERC1155', show: { 'actions.operation': ['nftBalance'] } } ] as INodeParams[] export const nftParams = [ { label: 'Collection Contract Address', name: 'collection_contract_address', type: 'string', placeholder: '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d', show: { 'actions.operation': [ 'nftTransactions', 'nftTransfers', 'nftOrders', 'nftCollectionStatistics', 'nftStatistics', 'nftInfo', 'nftAttributes', 'nftCollectionsByChain', 'nftBalance' ] } }, { label: 'Type', name: 'type', type: 'options', options: [ { label: 'Listing', name: 'listing' }, { label: 'Bidding', name: 'bidding' } ], show: { 'actions.operation': ['nftOrders'] } }, { label: 'Status', name: 'status', type: 'options', options: [ { label: 'Active', name: 'active' }, { label: 'Expired', name: 'expired' }, { label: 'Cancelled', name: 'cancelled' }, { label: 'Filled', name: 'filled' }, { label: 'Inactive', name: 'inactive' } ], show: { 'actions.operation': ['nftOrders'] } }, { label: 'Statistics Metrics', name: 'statistics_metrics', type: 'options', options: [ { label: 'Market Cap', name: 'market_cap' }, { label: 'Volume', name: 'volume' }, { label: 'Floor Price', name: 'floor_price' } ], show: { 'actions.operation': ['nftCollectionStatistics'] } }, { label: 'Statistics Time Model', name: 'statistics_time_model', type: 'options', options: [ { label: 'Latest', name: 'latest' } ], default: 'latest', show: { 'actions.operation': ['nftStatistics'] } }, { label: 'NFT Token Id', name: 'nft_token_id', type: 'string', placeholder: '1', optional: true, show: { 'actions.operation': ['nftStatistics', 'nftInfo', 'nftAttributes'] } } ] as INodeParams[] export const timeRangeParams = [ { label: 'Start Time', name: 'start_time', type: 'date', description: 'The query supports time period query, when start_time is specified, but end_time is not specified, the default query is the data of 24 hours after start_time (including start_time); if you do not specify start_time and end_time, the default query is yesterday,the maximum span of one inquiry is 30 days , eg: 2022-02-02 00:00:00', optional: true, show: { 'actions.operation': [ 'nftTransactions', 'nftTransfers', 'nftOrders', 'nftCollectionStatistics', 'nftCollectionsByChain', 'tokenTransfers', 'protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'chainTransactions' ] } }, { label: 'End Time', name: 'end_time', type: 'date', description: 'When end_time is specified, but no start_time is specified, the default query is 24 hours before end_time (not including end_time) , eg: 2022-02-03 00:00:00', optional: true, show: { 'actions.operation': [ 'nftTransactions', 'nftTransfers', 'nftOrders', 'nftCollectionStatistics', 'nftCollectionsByChain', 'tokenTransfers', 'protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'chainTransactions' ] } } ] as INodeParams[] export const limitOffetParams = [ { label: 'Limit', name: 'limit', type: 'number', description: 'Page size, Defaults to 100, capped at 100, eg: 100', optional: true, show: { 'actions.operation': [ 'nftTransactions', 'nftTransfers', 'nftOrders', 'nftCollectionStatistics', 'nftStatistics', 'nftInfo', 'nftAttributes', 'nftCollectionsByChain', 'nftBalanceByWallet', 'tokenTransfers', 'allTokenBalance', 'protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'protocolsByChain', 'chainTransactions' ] } }, { label: 'Offset', name: 'offset', type: 'number', description: 'The offset for pagination, capped at 80000 eg:120', optional: true, show: { 'actions.operation': [ 'nftTransactions', 'nftTransfers', 'nftOrders', 'nftCollectionStatistics', 'nftStatistics', 'nftInfo', 'nftAttributes', 'nftCollectionsByChain', 'nftBalanceByWallet', 'tokenTransfers', 'allTokenBalance', 'protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'protocolsByChain', 'chainTransactions' ] } } ] as INodeParams[] export const tokenParams = [ { label: 'Token Address', name: 'token_address', type: 'string', description: 'Token contract address', placeholder: '0xcc079ca45b62c5bc0064e0b40a2dd3d409503119', optional: true, show: { 'actions.operation': ['tokenTransfers'] } }, { label: 'From Address', name: 'from_address', type: 'string', description: 'Address of the sender', placeholder: '0x21787ae502b65d52071daa6519d7b6258758da5e', optional: true, show: { 'actions.operation': ['tokenTransfers', 'chainTransactions'] } }, { label: 'To Address', name: 'to_address', type: 'string', description: 'Address of the receiver', placeholder: '0x4ba50c4469f4c1388745739d9d73010781617688', optional: true, show: { 'actions.operation': ['tokenTransfers', 'chainTransactions'] } } ] as INodeParams[] export const tokenBalanceParams = [ { label: 'Token Address', name: 'token_address', type: 'string', description: 'Token contract address', placeholder: '0x4fabb145d64652a948d72533023f6e7a623c7c53', show: { 'actions.operation': ['tokenBalance'] } }, { label: 'Wallet Address', name: 'wallet_address', type: 'string', description: 'Wallet address', placeholder: '0xb8001c3ec9aa1985f6c747e25c28324e4a361ec1', show: { 'actions.operation': ['tokenBalance', 'allTokenBalance'] } } ] as INodeParams[] export const gamefiParams = [ { label: 'Protocol Slug', name: 'protocol_slug', type: 'string', description: 'The protocol slug of the transactions, eg: aave', placeholder: 'aave', show: { 'actions.operation': ['protocolTransactions', 'protocolAddresses', 'protocolUserStatistics', 'protocolsByChain'] } }, { label: 'Address Type', name: 'address_type', type: 'options', options: [ { label: 'Active', name: 'active' } ], default: 'active', show: { 'actions.operation': ['protocolAddresses'] } }, { label: 'Statistics Frequency Model', name: 'statistics_frequency_model', type: 'options', options: [ { label: 'Daily', name: 'daily' } ], optional: true, show: { 'actions.operation': ['protocolUserStatistics'] } }, { label: 'Contract Address', name: 'contract_address', type: 'string', description: 'The contract address of the transactions', placeholder: '0x794a61358d6845594f94dc1db02a252b5b4814ad', optional: true, show: { 'actions.operation': ['protocolTransactions'] } }, { label: 'Wallet Address', name: 'wallet_address', type: 'string', description: 'The wallet address of the transactions', placeholder: '0xbd90be3937744e2cd0ee680807901b1ab9479ffc', optional: true, show: { 'actions.operation': ['protocolTransactions'] } } ] as INodeParams[] ================================================ FILE: packages/components/nodes/GitHub/GitHub.ts ================================================ import axios, { AxiosRequestConfig, AxiosRequestHeaders, Method } from 'axios' import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' class GitHub implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'GitHub' this.name = 'GitHub' this.icon = 'GitHub-Logo.png' this.type = 'action' this.category = 'Development' this.version = 1.0 this.description = 'GitHub API' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Operation', name: 'operation', type: 'options', options: [ { label: 'List User Repositories', name: 'listUserRepositories', description: 'Lists public repositories for the specified user.' }, { label: 'List Organization Repositories', name: 'listOrganizationRepositories', description: 'Lists repositories for the specified organization.' }, { label: 'List repository issues', name: 'listRepositoryIssues', description: 'List issues in a repository.' }, { label: 'Create an issue', name: 'createIssue', description: 'Any user with pull access to a repository can create an issue.' }, { label: 'Get an issue', name: 'getIssue', description: 'Get an issues in a repository.' }, { label: 'Update an issue', name: 'updateIssue', description: 'Update an issues in a repository.' }, { label: 'Lock an issue', name: 'lockIssue', description: "Users with push access can lock an issue or pull request's conversation." }, { label: 'Unlock an issue', name: 'unlockIssue', description: "Users with push access can unlock an issue's conversation." } ] } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'GitHub API', name: 'gitHubApi' } ], default: 'gitHubApi' } ] as INodeParams[] this.inputParameters = [ { label: 'Owner/Organization', name: 'owner', type: 'string', show: { 'actions.operation': [ 'listRepositoryIssues', 'createIssue', 'getIssue', 'updateIssue', 'lockIssue', 'unlockIssue', 'listUserRepositories', 'listOrganizationRepositories' ] }, description: 'The account owner of the repository. The name is not case sensitive.' }, { label: 'Type', name: 'orgType', type: 'options', options: [ { label: 'All', name: 'all' }, { label: 'Public', name: 'public' }, { label: 'Private', name: 'private' }, { label: 'Forks', name: 'forks' }, { label: 'Sources', name: 'sources' }, { label: 'Member', name: 'member' }, { label: 'Internal', name: 'internal' } ], show: { 'actions.operation': ['listOrganizationRepositories'] }, description: 'Specifies the types of repositories you want returned.', optional: true, default: 'all' }, { label: 'Type', name: 'userType', type: 'options', options: [ { label: 'All', name: 'all' }, { label: 'Owner', name: 'owner' }, { label: 'Member', name: 'member' } ], show: { 'actions.operation': ['listUserRepositories'] }, description: 'Specifies the types of repositories you want returned.', optional: true, default: 'owner' }, { label: 'Sort', name: 'sort', type: 'options', options: [ { label: 'Created', name: 'created' }, { label: 'Updated', name: 'updated' }, { label: 'Pushed', name: 'pushed' }, { label: 'Full Name', name: 'full_name' } ], show: { 'actions.operation': ['listUserRepositories', 'listOrganizationRepositories'] }, description: 'The property to sort the results by.', optional: true, default: 'created' }, { label: 'Direction of sort', name: 'direction', type: 'options', options: [ { label: 'Ascending', name: 'asc' }, { label: 'Descending', name: 'desc' } ], show: { 'actions.operation': ['listUserRepositories', 'listOrganizationRepositories'] }, description: 'The order to sort by.', optional: true, default: 'desc' }, { label: 'Repository', name: 'repo', type: 'string', show: { 'actions.operation': ['listRepositoryIssues', 'createIssue', 'getIssue', 'updateIssue', 'lockIssue', 'unlockIssue'] }, description: 'The name of the repository. The name is not case sensitive.' }, { label: 'Title', name: 'title', type: 'string', show: { 'actions.operation': ['createIssue'] }, description: 'The title of the issue.' }, { label: 'Title', name: 'titleOptional', type: 'string', show: { 'actions.operation': ['updateIssue'] }, description: 'The title of the issue.', optional: true }, { label: 'Body', name: 'body', type: 'string', show: { 'actions.operation': ['createIssue', 'updateIssue'] }, description: 'The contents of the issue.', optional: true }, { label: 'Issue Number', name: 'issueNumber', type: 'number', show: { 'actions.operation': ['getIssue', 'updateIssue', 'lockIssue', 'unlockIssue'] }, description: 'The number that identifies the issue.' }, { label: 'Lock Reason', name: 'lockReason', type: 'options', options: [ { label: 'Off Topic', name: 'off-topic' }, { label: 'Too Heated', name: 'too heated' }, { label: 'Resolved', name: 'resolved' }, { label: 'Spam', name: 'spam' } ], show: { 'actions.operation': ['lockIssue'] }, description: 'The reason for locking the issue or pull request conversation.', optional: true } ] as INodeParams[] } async run(nodeData: INodeData): Promise { const returnData: ICommonObject[] = [] const actionData = nodeData.actions const inputParametersData = nodeData.inputParameters const credentials = nodeData.credentials if (actionData === undefined || inputParametersData === undefined || credentials === undefined) { throw new Error('Required data missing') } const operation = actionData.operation as string const accessToken = credentials.accessToken as string let responseData: any try { let method: Method = 'GET' let url = '' const queryParameters: ICommonObject = {} const headers: AxiosRequestHeaders = { 'Content-Type': 'application/json', Authorization: 'Bearer ' + accessToken } let dataString: any = {} const queryBody: ICommonObject = {} if (operation === 'listUserRepositories' || operation === 'listOrganizationRepositories') { const owner = inputParametersData.owner as string const type = operation === 'listUserRepositories' ? inputParametersData.userType : inputParametersData.orgType const path = operation === 'listUserRepositories' ? 'users' : 'orgs' const sort = inputParametersData.sort as string const direction = inputParametersData.direction as string method = 'GET' url = `https://api.github.com/${path}/${owner}/repos` if (type) url += url.includes('?') ? `&type=${type};` : `?type=${type}` if (sort) url += url.includes('?') ? `&sort=${sort};` : `?sort=${sort}` if (direction) url += url.includes('?') ? `&direction=${direction};` : `?direction=${direction}` } else if (operation === 'listRepositoryIssues') { const owner = inputParametersData.owner as string const repo = inputParametersData.repo as string method = 'GET' url = 'https://api.github.com/repos/' + owner + '/' + repo + '/issues' } else if (operation === 'createIssue') { const owner = inputParametersData.owner as string const repo = inputParametersData.repo as string const title = inputParametersData.title as string const body = inputParametersData.body as string method = 'POST' url = 'https://api.github.com/repos/' + owner + '/' + repo + '/issues' if (title) dataString['title'] = title if (body) dataString['body'] = body } else if (operation === 'getIssue') { const owner = inputParametersData.owner as string const repo = inputParametersData.repo as string const issueNumber = inputParametersData.issueNumber as string method = 'GET' url = 'https://api.github.com/repos/' + owner + '/' + repo + '/issues/' + issueNumber } else if (operation === 'updateIssue') { const owner = inputParametersData.owner as string const repo = inputParametersData.repo as string const issueNumber = inputParametersData.issueNumber as string const title = inputParametersData.titleOptional as string const body = inputParametersData.body as string method = 'PATCH' url = 'https://api.github.com/repos/' + owner + '/' + repo + '/issues/' + issueNumber if (title) dataString['title'] = title if (body) dataString['body'] = body } else if (operation === 'lockIssue') { const owner = inputParametersData.owner as string const repo = inputParametersData.repo as string const issueNumber = inputParametersData.issueNumber as string const lockReason = inputParametersData.lockReason as string method = 'PUT' url = 'https://api.github.com/repos/' + owner + '/' + repo + '/issues/' + issueNumber + '/lock' if (lockReason) dataString['lock_reason'] = lockReason } else if (operation === 'unlockIssue') { const owner = inputParametersData.owner as string const repo = inputParametersData.repo as string const issueNumber = inputParametersData.issueNumber as string method = 'DELETE' url = 'https://api.github.com/repos/' + owner + '/' + repo + '/issues/' + issueNumber + '/lock' } const axiosConfig: AxiosRequestConfig = { method, url, params: queryParameters, paramsSerializer: (params) => serializeQueryParams(params), headers, data: dataString } if (Object.keys(queryBody).length > 0) { axiosConfig.data = queryBody } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: GitHub } ================================================ FILE: packages/components/nodes/GitHub/GitHubWebhook.ts ================================================ import axios, { AxiosRequestConfig, AxiosRequestHeaders, Method } from 'axios' import { ICommonObject, INode, INodeData, INodeParams, IWebhookNodeExecutionData, NodeType } from '../../src/Interface' import { returnWebhookNodeExecutionData } from '../../src/utils' class GitHubWebhook implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'GitHub Webhook' this.name = 'GitHubWebhook' this.icon = 'GitHub-Logo.png' this.type = 'webhook' this.category = 'Development' this.version = 1.0 this.description = 'Start workflow whenever GitHub webhook event happened' this.incoming = 0 this.outgoing = 1 this.actions = [ { label: 'Events', name: 'events', type: 'options', options: [ { label: 'Branch Protection Rule', name: 'branch_protection_rule', description: 'Activity related to a branch protection rule.' }, { label: 'Check Run', name: 'check_run', description: 'Check run activity has occurred.' }, { label: 'Check Suite', name: 'check_suite', description: 'Check suite activity has occurred.' }, { label: 'Code Scanning Alert', name: 'code_scanning_alert', description: 'Activity related to code scanning alerts in a repository.' }, { label: 'Commit Comment', name: 'commit_comment', description: 'A commit comment is created.' }, { label: 'Create', name: 'create', description: 'A Git branch or tag is created.' }, { label: 'Delete', name: 'delete', description: 'A Git branch or tag is deleted.' }, { label: 'Dependabot Alert', name: 'dependabot_alert', description: 'Activity related to Dependabot alerts.' }, { label: 'Deploy Key', name: 'deploy_key', description: 'A deploy key is added or removed from a repository.' }, { label: 'Deployment', name: 'deployment', description: 'A deployment is created.' }, { label: 'Deployment Status', name: 'deployment_status', description: 'A deployment is created.' }, { label: 'Discussion', name: 'discussion', description: 'Activity related to a discussion.' }, { label: 'Discussion Comment', name: 'discussion_comment', description: 'Activity related to a comment in a discussion.' }, { label: 'Fork', name: 'fork', description: 'A user forks a repository.' }, { label: 'Github App Authorization', name: 'github_app_authorization', description: 'When someone revokes their authorization of a GitHub App, this event occurs. A GitHub App receives this webhook by default and cannot unsubscribe from this event.' }, { label: 'Gollum', name: 'gollum', description: 'A wiki page is created or updated.' }, { label: 'Installation', name: 'installation', description: 'Activity related to a GitHub App installation.' }, { label: 'Installation Repositories', name: 'installation_repositories', description: 'Activity related to repositories being added to a GitHub App installation.' }, { label: 'Issue Comment', name: 'issue_comment', description: 'Activity related to an issue or pull request comment. ' }, { label: 'Issues', name: 'issues', description: 'Activity related to an issue.' }, { label: 'Label', name: 'label', description: 'Activity related to a label.' }, { label: 'Marketplace Purchase', name: 'marketplace_purchase', description: 'Activity related to a GitHub Marketplace purchase.' }, { label: 'Member', name: 'member', description: 'Activity related to repository collaborators.' }, { label: 'Membership', name: 'membership', description: 'Activity related to team membership.' }, { label: 'Merge Group', name: 'merge_group', description: 'Activity related to merge groups in a merge queue.' }, { label: 'Meta', name: 'meta', description: 'The webhook this event is configured on was deleted.' }, { label: 'Milestone', name: 'milestone', description: 'Activity related to milestones.' }, { label: 'Organization', name: 'organization', description: 'Activity related to an organization and its members.' }, { label: 'Org Block', name: 'org_block', description: 'Activity related to people being blocked in an organization. ' }, { label: 'Package', name: 'package', description: 'Activity related to GitHub Packages.' }, { label: 'Page Build', name: 'page_build', description: 'Represents an attempted build of a GitHub Pages site,' }, { label: 'Ping', name: 'ping', description: 'When you create a new webhook, we send you a simple ping event to let you know you have set up the webhook correctly.' }, { label: 'Project', name: 'project', description: 'Activity related to classic projects.' }, { label: 'Project Card', name: 'project_card', description: 'Activity related to cards in a classic project.' }, { label: 'Project Column', name: 'project_column', description: 'Activity related to columns in a classic project. ' }, { label: 'Projects V2 Item', name: 'projects_v2_item', description: 'Activity related to items in a project.' }, { label: 'Public', name: 'public', description: 'When a private repository is made public.' }, { label: 'Pull Request', name: 'pull_request', description: 'Activity related to pull requests.' }, { label: 'Pull Request Review', name: 'pull_request_review', description: 'Activity related to pull request reviews.' }, { label: 'Pull Request Review Comment', name: 'pull_request_review_comment', description: 'Activity related to pull request review comments in the pull request unified diff.' }, { label: 'Pull Request Review Thread', name: 'pull_request_review_thread', description: 'Activity related to a comment thread on a pull request being marked as resolved or unresolved.' }, { label: 'Push', name: 'push', description: 'Activity related to one or more commits pushed to a repository branch or tag.' }, { label: 'Release', name: 'release', description: 'Activity related to a release.' }, { label: 'Repository Dispatch', name: 'repository_dispatch', description: 'This event occurs when a GitHub App sends a POST request to the "Create a repository dispatch event" endpoint.' }, { label: 'Repository', name: 'repository', description: 'Activity related to a repository.' }, { label: 'Repository Import', name: 'repository_import', description: 'Activity related to a repository being imported to GitHub.' }, { label: 'Repository Vulnerability Alert', name: 'repository_vulnerability_alert', description: 'Activity related to security vulnerability alerts in a repository.' }, { label: 'Security Advisory', name: 'security_advisory', description: 'Activity related to a security advisory that has been reviewed by GitHub. A GitHub-reviewed security advisory provides information about security-related vulnerabilities in software on GitHub.' }, { label: 'Sponsorship', name: 'sponsorship', description: 'Activity related to a sponsorship listing. The type of activity is specified in the action property of the payload object.' }, { label: 'Star', name: 'star', description: 'Activity related to a repository being starred.' }, { label: 'Status', name: 'status', description: 'When the status of a Git commit changes.' }, { label: 'Team', name: 'team', description: 'Activity related to an organization team.' }, { label: 'Team Add', name: 'team_add', description: 'When a repository is added to a team.' }, { label: 'Watch', name: 'watch', description: 'When someone stars a repository.' }, { label: 'Workflow Dispatch', name: 'workflow_dispatch', description: 'This event occurs when someone triggers a workflow run on GitHub or sends a POST request to the "Create a workflow dispatch event" endpoint.' }, { label: 'Workflow Job', name: 'workflow_job', description: 'A GitHub Actions workflow job has been queued, is in progress, or has been completed on a repository.' }, { label: 'Workflow Run', name: 'workflow_run', description: 'When a GitHub Actions workflow run is requested or completed.' } ], default: 'push' } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'GitHub API', name: 'gitHubApi' } ], default: 'gitHubApi' } ] as INodeParams[] this.inputParameters = [ { label: 'Owner/Organization', name: 'owner', type: 'string', description: 'The account owner of the repository. The name is not case sensitive.' }, { label: 'Repository', name: 'repo', type: 'string', description: 'The name of the repository. The name is not case sensitive.' } ] as INodeParams[] } webhookMethods = { async createWebhook(nodeData: INodeData, webhookFullUrl: string): Promise { // Check if webhook exists const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters const actionsData = nodeData.actions if (inputParametersData === undefined || actionsData === undefined) { throw new Error('Required data missing') } if (credentials === undefined) { throw new Error('Missing credentials') } const owner = inputParametersData.owner as string const repo = inputParametersData.repo as string const baseUrl = `https://api.github.com/repos/${owner}/${repo}/hooks` const accessToken = credentials.accessToken as string const headers: AxiosRequestHeaders = { Authorization: 'Bearer ' + accessToken } const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `${baseUrl}?per_page=100`, headers } let webhookExist = false const webhook_events = [] try { const responseListWebhooks = await axios(axiosConfig) const responseWebhooks = responseListWebhooks.data webhook_events.push(actionsData.events as string) for (const webhook of responseWebhooks) { if (webhook.events === webhook_events && webhook.config.url === webhookFullUrl) { webhookExist = true break } } } catch (err) { if (err.response.status !== 404) throw new Error(err) } if (!webhookExist) { try { const data: ICommonObject = { name: 'web', config: { content_type: 'json', insecure_ssl: '0', url: webhookFullUrl }, events: webhook_events, active: true } const axiosCreateConfig: AxiosRequestConfig = { method: 'POST' as Method, url: baseUrl, data, headers } const createResponse = await axios(axiosCreateConfig) const createResponseData = createResponse.data if (createResponseData && createResponseData.id) { return createResponseData.id } return } catch (error) { return } } }, async deleteWebhook(nodeData: INodeData, webhookId: string): Promise { const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters const actionsData = nodeData.actions if (inputParametersData === undefined || actionsData === undefined) { throw new Error('Required data missing') } if (credentials === undefined) { throw new Error('Missing credentials') } const owner = inputParametersData.owner as string const repo = inputParametersData.repo as string const baseUrl = `https://api.github.com/repos/${owner}/${repo}/hooks` const accessToken = credentials.accessToken as string const headers: AxiosRequestHeaders = { Authorization: 'Bearer ' + accessToken } const axiosConfig: AxiosRequestConfig = { method: 'DELETE' as Method, url: `${baseUrl}/${webhookId}`, headers } try { await axios(axiosConfig) } catch (error) { return false } return true } } async runWebhook(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters const req = nodeData.req if (inputParametersData === undefined) { throw new Error('Required data missing') } if (req === undefined) { throw new Error('Missing request') } // Check if it is a ping event, if yes return null if (req.headers && req.headers['x-github-event'] === 'ping') return null const returnData: ICommonObject[] = [] returnData.push({ headers: req?.headers, params: req?.params, query: req?.query, body: req?.body, rawBody: (req as any).rawBody, url: req?.url }) return returnWebhookNodeExecutionData(returnData) } } module.exports = { nodeClass: GitHubWebhook } ================================================ FILE: packages/components/nodes/GoogleDocs/GoogleDocs.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, refreshOAuth2Token, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' import axios, { AxiosRequestConfig, AxiosRequestHeaders, Method } from 'axios' class GoogleDocs implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'GoogleDocs' this.name = 'googleDocs' this.icon = 'gdocs.svg' this.type = 'action' this.category = 'Productivity' this.version = 1.0 this.description = 'Execute GoogleDocs operations' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Operation', name: 'operation', type: 'options', options: [ { label: 'Create New Document', name: 'create', description: 'Create a new document' }, { label: 'Get All Values', name: 'getAll', description: 'Get all values from a document' }, { label: 'Update a Document', name: 'update', description: 'Update a document' } ], default: 'create' } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Google Docs OAuth2', name: 'googleDocsOAuth2Api' } ], default: 'googleDocsOAuth2Api' } ] as INodeParams[] this.inputParameters = [ { label: 'Document Name', name: 'documentName', type: 'string', optional: true, description: 'Name of the document to create. Default to Untitled document.', show: { 'actions.operation': ['create'] } }, { label: 'Document', name: 'documentId', type: 'asyncOptions', loadMethod: 'getAllDocsFromDrive', hide: { 'actions.operation': ['create'] } }, /** * batch update */ { label: 'Requests', name: 'requests', description: "update a document. You can simply add one reequest data or add multiple request data. If request format is invalid, document won't be updated. The details about how to write a request data can be found at https://developers.google.com/docs/api/reference/rest/v1/documents/batchUpdate", type: 'json', placeholder: `[ { "insertText": { "text": "new text", "location": { "index": 1 } } }, { "insertTable": { "rows": 3, "columns": 4, "endOfSegmentLocation":{ } } } ]`, show: { 'actions.operation': ['update'] } } ] as INodeParams[] } loadMethods = { async getAllDocsFromDrive(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const credentials = nodeData.credentials if (credentials === undefined) { return returnData } // Get credentials const token_type = credentials!.token_type as string const access_token = credentials!.access_token as string const headers: AxiosRequestHeaders = { 'Content-Type': 'application/json', Authorization: `${token_type} ${access_token}` } const axiosConfig: AxiosRequestConfig = { method: 'GET', url: `https://www.googleapis.com/drive/v3/files?q=mimeType='application/vnd.google-apps.document'`, headers } let maxRetries = 5 do { try { const response = await axios(axiosConfig) const responseData = response.data for (const file of responseData.files || []) { returnData.push({ label: file.name as string, name: file.id as string }) } return returnData } catch (e) { // Access_token expired if (e.response && e.response.status === 401) { const { access_token } = await refreshOAuth2Token(credentials) headers['Authorization'] = `${token_type} ${access_token}` continue } return returnData } } while (--maxRetries) return returnData } } async run(nodeData: INodeData): Promise { const actionsData = nodeData.actions const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (actionsData === undefined) { throw new Error('Required data missing!') } if (credentials === undefined) { throw new Error('Missing credential!') } // Get operation const operation = actionsData.operation as string // Get credentials const token_type = credentials!.token_type as string const access_token = credentials!.access_token as string const returnData: ICommonObject[] = [] let responseData: any let url = '' const queryParameters: ICommonObject = {} let queryBody: any = {} let method: Method = 'POST' const headers: AxiosRequestHeaders = { 'Content-Type': 'application/json', Authorization: `${token_type} ${access_token}` } const documentId = inputParametersData?.documentId as string let maxRetries = 5 let oAuth2RefreshedData: any = {} do { try { if (operation === 'create') { url = 'https://docs.googleapis.com/v1/documents' const documentName = inputParametersData?.documentName as string if (documentName) { queryBody['title'] = documentName } } else if (operation === 'getAll') { method = 'GET' url = `https://docs.googleapis.com/v1/documents/${documentId}` } else if (operation === 'update') { // batch update url = `https://docs.googleapis.com/v1/documents/${documentId}:batchUpdate` const requestsString = inputParametersData?.requests as string queryBody['requests'] = JSON.parse(requestsString) } const axiosConfig: AxiosRequestConfig = { method, url, headers } if (Object.keys(queryParameters).length > 0) { axiosConfig.params = queryParameters axiosConfig.paramsSerializer = (params) => serializeQueryParams(params) } if (Object.keys(queryBody).length > 0) { axiosConfig.data = queryBody } const response = await axios(axiosConfig) responseData = response.data break } catch (error) { // Access_token expired if (error.response && error.response.status === 401) { const { access_token, expires_in } = await refreshOAuth2Token(credentials) headers['Authorization'] = `${token_type} ${access_token}` oAuth2RefreshedData = { access_token, expires_in } continue } throw handleErrorMessage(error) } } while (--maxRetries) if (maxRetries <= 0) { throw new Error('Error executing GoogleDocs node. Max retries limit was reached.') } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData, oAuth2RefreshedData) } } module.exports = { nodeClass: GoogleDocs } ================================================ FILE: packages/components/nodes/GoogleSheet/GoogleSheet.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, refreshOAuth2Token, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' import axios, { AxiosRequestConfig, AxiosRequestHeaders, Method } from 'axios' class GoogleSheet implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'GoogleSheet' this.name = 'googleSheet' this.icon = 'gsheet.svg' this.type = 'action' this.category = 'Productivity' this.version = 1.0 this.description = 'Execute GoogleSheet operations' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Operation', name: 'operation', type: 'options', options: [ { label: 'Create New SpreadSheet', name: 'create', description: 'Create a new spreadsheet' }, { label: 'Add Rows', name: 'addRows', description: 'Add multiple rows to a spreadsheet' }, { label: 'Get All Values', name: 'getAll', description: 'Get all values from a spreadsheet' }, { label: 'Get Values By Range', name: 'getRange', description: 'Get values from cells from specified range' }, { label: 'Update Cell', name: 'updateCell', description: 'Update single cell in a spreadsheet' }, { label: 'Update Rows', name: 'updateRows', description: 'Update multiple rows in a spreadsheet' }, { label: 'Clear Row', name: 'clearRow', description: 'Clear the values of a row in a spreadsheet' }, { label: 'Clear Column', name: 'clearCol', description: 'Clear the values of a column in a spreadsheet' }, { label: 'Clear By Range', name: 'clearRange', description: 'Clear the values in a spreadsheet from specified range' }, { label: 'Clear All Values', name: 'clearAll', description: 'Clear all the values in a spreadsheet' } ], default: 'create' } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Google Sheet OAuth2', name: 'googleSheetsOAuth2Api' } ], default: 'googleSheetsOAuth2Api' } ] as INodeParams[] this.inputParameters = [ { label: 'Spreadsheet Name', name: 'spreadsheetName', type: 'string', optional: true, description: 'Name of the spreadsheet to create. Default to Untitled spreadsheet.', show: { 'actions.operation': ['create'] } }, { label: 'Spreadsheet', name: 'spreadsheetId', type: 'asyncOptions', loadMethod: 'getAllSheetsFromDrive', hide: { 'actions.operation': ['create'] } }, { label: 'Sheet Name', name: 'sheetName', type: 'asyncOptions', loadMethod: 'getSheets', hide: { 'actions.operation': ['create'] } }, /*** * Get Values By Range */ { label: 'Range', name: 'range', type: 'string', placeholder: 'A1:B2', description: 'For example, if the spreadsheet data is: A1=1, B1=2, A2=3, B2=4, then requesting range=A1:B2, returns [[1, 2],[3, 4]]', show: { 'actions.operation': ['getRange'] } }, /*** * Add Rows */ { label: 'Row Values', name: 'rowValues', type: 'json', placeholder: '[[1, 2], [3, 4]]', description: 'For instance: [[1, 2], [3, 4]] will insert 2 new rows. FIRST row: A1=1, B1=2, and SECOND row: A2=3, B2=4', show: { 'actions.operation': ['addRows'] } }, /*** * Update Rows */ { label: 'Range', name: 'range', type: 'string', placeholder: 'A1:B4', description: 'A1 notation of the rows to update.', show: { 'actions.operation': ['updateRows'] } }, { label: 'Row Values', name: 'rowValues', type: 'json', placeholder: '[[1, 2], [3, 4]]', description: 'For instance: [[1, 2], [3, 4]] will update FIRST row: A1=1, B1=2, and SECOND row: A2=3, B2=4', show: { 'actions.operation': ['updateRows'] } }, /*** * Update Cell */ { label: 'Cell', name: 'range', type: 'string', placeholder: 'A1', description: 'A1 notation of the cell to update.', show: { 'actions.operation': ['updateCell'] } }, { label: 'Cell Value', name: 'cellValue', type: 'string', placeholder: 'New Update Value', default: '', description: 'New value of the cell', show: { 'actions.operation': ['updateCell'] } }, /*** * Clear */ { label: 'Row Number', name: 'clearRowNumber', type: 'number', placeholder: '1', description: 'Row (number) to clear the values', show: { 'actions.operation': ['clearRow'] } }, { label: 'Column Number', name: 'clearColNumber', type: 'string', placeholder: 'A', description: 'Column (alphabet) to clear the values', show: { 'actions.operation': ['clearCol'] } }, { label: 'Range', name: 'range', type: 'string', placeholder: 'A1:B4', description: 'A1 notation of the range to clear.', show: { 'actions.operation': ['clearRange'] } } ] as INodeParams[] } loadMethods = { async getAllSheetsFromDrive(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const credentials = nodeData.credentials if (credentials === undefined) { return returnData } // Get credentials const token_type = credentials!.token_type as string const access_token = credentials!.access_token as string const headers: AxiosRequestHeaders = { 'Content-Type': 'application/json', Authorization: `${token_type} ${access_token}` } const axiosConfig: AxiosRequestConfig = { method: 'GET', url: `https://www.googleapis.com/drive/v3/files?q=mimeType='application/vnd.google-apps.spreadsheet'`, headers } let maxRetries = 5 do { try { const response = await axios(axiosConfig) const responseData = response.data for (const file of responseData.files || []) { returnData.push({ label: file.name as string, name: file.id as string }) } return returnData } catch (e) { // Access_token expired if (e.response && e.response.status === 401) { const { access_token } = await refreshOAuth2Token(credentials) headers['Authorization'] = `${token_type} ${access_token}` continue } return returnData } } while (--maxRetries) return returnData }, async getSheets(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const credentials = nodeData.credentials const inputParameters = nodeData.inputParameters if (credentials === undefined || inputParameters === undefined) { return returnData } const spreadsheetId = inputParameters.spreadsheetId as string const token_type = credentials!.token_type as string const access_token = credentials!.access_token as string const headers: AxiosRequestHeaders = { 'Content-Type': 'application/json', Authorization: `${token_type} ${access_token}` } if (spreadsheetId === undefined || token_type === undefined || access_token === undefined) { return returnData } const axiosConfig: AxiosRequestConfig = { method: 'GET', url: `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}`, headers } let maxRetries = 5 do { try { const response = await axios(axiosConfig) const responseData = response.data for (const sheet of responseData.sheets!) { if (sheet.properties!.sheetType !== 'GRID') continue returnData.push({ label: sheet.properties!.title as string, name: sheet.properties!.title as string }) } return returnData } catch (e) { // Access_token expired if (e.response && e.response.status === 401) { const { access_token } = await refreshOAuth2Token(credentials) headers['Authorization'] = `${token_type} ${access_token}` continue } return returnData } } while (--maxRetries) return returnData } } async run(nodeData: INodeData): Promise { const actionsData = nodeData.actions const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (actionsData === undefined) { throw new Error('Required data missing!') } if (credentials === undefined) { throw new Error('Missing credential!') } // Get operation const operation = actionsData.operation as string // Get credentials const token_type = credentials!.token_type as string const access_token = credentials!.access_token as string const returnData: ICommonObject[] = [] let responseData: any let url = '' const queryParameters: ICommonObject = {} let queryBody: any = {} let method: Method = 'POST' const headers: AxiosRequestHeaders = { 'Content-Type': 'application/json', Authorization: `${token_type} ${access_token}` } const spreadsheetId = inputParametersData?.spreadsheetId as string const sheetName = inputParametersData?.sheetName as string const rowValues = inputParametersData?.rowValues as string let maxRetries = 5 let oAuth2RefreshedData: any = {} do { try { if (operation === 'create') { url = 'https://sheets.googleapis.com/v4/spreadsheets' const spreadsheetName = inputParametersData?.spreadsheetName as string if (spreadsheetName) { queryBody['properties'] = { title: spreadsheetName } } } else if (operation === 'addRows') { const rows = JSON.parse(rowValues.replace(/\s/g, '')) const range = sheetName url = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${encodeURIComponent(range)}:append` queryParameters['valueInputOption'] = 'USER_ENTERED' queryParameters['insertDataOption'] = 'INSERT_ROWS' queryBody['values'] = rows } else if (operation === 'getAll') { method = 'GET' const range = sheetName url = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${encodeURIComponent(range)}` } else if (operation === 'getRange') { method = 'GET' const range = inputParametersData?.range as string url = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${encodeURIComponent(range)}` } else if (operation === 'updateCell') { method = 'PUT' const range = inputParametersData?.range as string const cellValue = inputParametersData?.cellValue as string url = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${encodeURIComponent(range)}` queryParameters['valueInputOption'] = 'USER_ENTERED' queryBody['values'] = [[cellValue]] } else if (operation === 'updateRows') { method = 'PUT' const rows = JSON.parse(rowValues.replace(/\s/g, '')) const range = inputParametersData?.range as string url = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${encodeURIComponent(range)}` queryParameters['valueInputOption'] = 'USER_ENTERED' queryBody['values'] = rows } else if (operation === 'clearRow') { const clearRowNumber = inputParametersData?.clearRowNumber as number const range = `${sheetName}!${clearRowNumber}:${clearRowNumber}` url = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${encodeURIComponent(range)}:clear` } else if (operation === 'clearCol') { const clearColNumber = inputParametersData?.clearColNumber as string const range = `${sheetName}!${clearColNumber}:${clearColNumber}` url = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${encodeURIComponent(range)}:clear` } else if (operation === 'clearRange') { const range = inputParametersData?.range as string url = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${encodeURIComponent(range)}:clear` } else if (operation === 'clearAll') { const range = sheetName url = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${encodeURIComponent(range)}:clear` } const axiosConfig: AxiosRequestConfig = { method, url, headers } if (Object.keys(queryParameters).length > 0) { axiosConfig.params = queryParameters axiosConfig.paramsSerializer = (params) => serializeQueryParams(params) } if (Object.keys(queryBody).length > 0) { axiosConfig.data = queryBody } const response = await axios(axiosConfig) responseData = response.data break } catch (error) { // Access_token expired if (error.response && error.response.status === 401) { const { access_token, expires_in } = await refreshOAuth2Token(credentials) headers['Authorization'] = `${token_type} ${access_token}` oAuth2RefreshedData = { access_token, expires_in } continue } throw handleErrorMessage(error) } } while (--maxRetries) if (maxRetries <= 0) { throw new Error('Error executing GoogleSheet node. Max retries limit was reached.') } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData, oAuth2RefreshedData) } } module.exports = { nodeClass: GoogleSheet } ================================================ FILE: packages/components/nodes/GraphQL/GraphQL.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import axios, { AxiosRequestConfig, AxiosRequestHeaders, Method } from 'axios' import isLocalhost from 'is-localhost-ip' class GraphQL implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'GraphQL' this.name = 'graphQL' this.icon = 'graphql.svg' this.type = 'action' this.category = 'Development' this.version = 1.0 this.description = 'Execute GraphQL request' this.incoming = 1 this.outgoing = 1 this.credentials = [ { label: 'Authorization', name: 'credentialMethod', type: 'options', options: [ { label: 'Basic Auth', name: 'httpBasicAuth' }, { label: 'Bearer Token Auth', name: 'httpBearerTokenAuth' }, { label: 'No Auth', name: 'noAuth', hideRegisteredCredential: true } ], default: 'noAuth' } ] as INodeParams[] this.inputParameters = [ { label: 'URL', name: 'url', type: 'string', default: '', placeholder: 'http://.com/' }, { label: 'Headers', name: 'headers', type: 'array', array: [ { label: 'Key', name: 'key', type: 'string', default: '' }, { label: 'Value', name: 'value', type: 'string', default: '' } ], optional: true }, { label: 'GraphQL Body', name: 'body', type: 'json', placeholder: `{ me { name } }`, optional: true }, { label: 'Variables', name: 'variables', type: 'json', placeholder: '{"var1": "value1"}', optional: true } ] } async run(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters const credentials = nodeData.credentials if (inputParametersData === undefined || credentials === undefined) { throw new Error('Required data missing') } const credentialMethod = credentials.credentialMethod as string const url = inputParametersData.url as string const headers = (inputParametersData.headers as ICommonObject[]) || [] const body = inputParametersData.body as string const variables = inputParametersData.variables as string const returnData: ICommonObject = {} const urlHost = new URL(url).hostname if ((await isLocalhost(urlHost)) || urlHost === '169.254.169.254' || urlHost === '[fd00:ec2::254]') { throw new Error('URL not allowed') } try { const queryHeaders: AxiosRequestHeaders = {} let data: any = {} for (const header of headers) { const key = header.key as string const value = header.value as string if (key) queryHeaders[key] = value } if (body) { data = { query: body.replace(/\s/g, ' ') } } if (variables) { const variablesJSON = JSON.parse(variables.replace(/\s/g, '')) if (Object.keys(variablesJSON).length) { data.variables = variablesJSON } } if (credentialMethod === 'httpBearerTokenAuth') { queryHeaders['Authorization'] = `Bearer ${credentials!.token}` } const axiosConfig: AxiosRequestConfig = { method: 'POST' as Method, url } if (Object.keys(data).length) { axiosConfig.data = data } if (Object.keys(queryHeaders).length) { axiosConfig.headers = queryHeaders } if (credentialMethod === 'httpBasicAuth') { axiosConfig.auth = { username: credentials!.userName as string, password: credentials!.password as string } } const response = await axios(axiosConfig) returnData['data'] = response.data returnData['status'] = response.status returnData['statusText'] = response.statusText returnData['headers'] = response.headers } catch (error) { throw handleErrorMessage(error) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: GraphQL } ================================================ FILE: packages/components/nodes/HTTP/HTTP.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' import axios, { AxiosRequestConfig, AxiosRequestHeaders, Method, ResponseType } from 'axios' import isLocalhost from 'is-localhost-ip' class HTTP implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'HTTP' this.name = 'http' this.icon = 'http.svg' this.type = 'action' this.category = 'Development' this.version = 1.0 this.description = 'Execute HTTP request' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Method', name: 'method', type: 'options', options: [ { label: 'GET', name: 'GET' }, { label: 'POST', name: 'POST' }, { label: 'PUT', name: 'PUT' }, { label: 'DELETE', name: 'DELETE' }, { label: 'HEAD', name: 'HEAD' } ], default: 'GET', description: 'HTTP method' } ] as INodeParams[] this.credentials = [ { label: 'Authorization', name: 'credentialMethod', type: 'options', options: [ { label: 'Basic Auth', name: 'httpBasicAuth' }, { label: 'Bearer Token Auth', name: 'httpBearerTokenAuth' }, { label: 'No Auth', name: 'noAuth', hideRegisteredCredential: true } ], default: 'noAuth' } ] as INodeParams[] this.inputParameters = [ { label: 'URL', name: 'url', type: 'string', default: '', placeholder: 'http://.com/' }, { label: 'Headers', name: 'headers', type: 'array', array: [ { label: 'Key', name: 'key', type: 'string', default: '' }, { label: 'Value', name: 'value', type: 'string', default: '' } ], optional: true }, { label: 'Query Params', name: 'queryParams', type: 'array', array: [ { label: 'Key', name: 'key', type: 'string', default: '' }, { label: 'Value', name: 'value', type: 'string', default: '' } ], optional: true }, { label: 'Body Type', name: 'bodyType', type: 'options', options: [ { label: 'JSON', name: 'json' }, { label: 'Text', name: 'text' } ], default: 'json', optional: true }, { label: 'Body', name: 'body', type: 'json', show: { 'inputParameters.bodyType': ['json'] }, placeholder: '{"var1": "value1"}', optional: true }, { label: 'Body', name: 'body', type: 'string', show: { 'inputParameters.bodyType': ['text'] }, default: '', optional: true }, { label: 'Response Type', name: 'responseType', type: 'options', options: [ { label: 'JSON', name: 'json' }, { label: 'Text', name: 'text' }, { label: 'Array Buffer', name: 'arraybuffer' }, { label: 'Raw (Base64)', name: 'base64' } ], optional: true } ] } async run(nodeData: INodeData): Promise { const actionData = nodeData.actions const inputParametersData = nodeData.inputParameters const credentials = nodeData.credentials if (actionData === undefined || inputParametersData === undefined || credentials === undefined) { throw new Error('Required data missing') } const method = actionData.method as string const credentialMethod = credentials.credentialMethod as string const url = inputParametersData.url as string const headers = (inputParametersData.headers as ICommonObject[]) || [] const queryParams = (inputParametersData.queryParams as ICommonObject[]) || [] const bodyType = inputParametersData.bodyType as string const body = inputParametersData.body as string const responseType = inputParametersData.responseType as string const returnData: ICommonObject = {} const urlHost = new URL(url).hostname if ((await isLocalhost(urlHost)) || urlHost === '169.254.169.254' || urlHost === '[fd00:ec2::254]') { throw new Error('URL not allowed') } try { const queryParameters: ICommonObject = {} const queryHeaders: AxiosRequestHeaders = {} let data: any = {} for (const params of queryParams) { const key = params.key as string const value = params.value as string if (key) queryParameters[key] = value } for (const header of headers) { const key = header.key as string const value = header.value as string if (key) queryHeaders[key] = value } if (bodyType && bodyType === 'json' && body) { data = JSON.parse(body.replace(/\s/g, ' ')) } else if (bodyType && bodyType === 'text' && body) { data = body } if (credentialMethod === 'httpBearerTokenAuth') { queryHeaders['Authorization'] = `Bearer ${credentials!.token}` } const axiosConfig: AxiosRequestConfig = { method: method as Method, url: url } if (Object.keys(data).length) { axiosConfig.data = data } if (Object.keys(queryParameters).length) { axiosConfig.params = queryParameters axiosConfig.paramsSerializer = (params) => serializeQueryParams(params) } if (Object.keys(queryHeaders).length) { axiosConfig.headers = queryHeaders } if (responseType) { axiosConfig.responseType = responseType as ResponseType if (responseType === 'base64') axiosConfig.responseType = 'arraybuffer' } if (credentialMethod === 'httpBasicAuth') { axiosConfig.auth = { username: credentials!.userName as string, password: credentials!.password as string } } const response = await axios(axiosConfig) if (responseType && responseType === 'base64') { const content = `data:${response.headers['content-type']};base64,${response.data.toString('base64')}` const attachment = { contentType: response.headers['content-type'], size: response.headers['content-length'], content } returnData['data'] = content returnData.attachments = [attachment] } else { returnData['data'] = response.data } returnData['status'] = response.status returnData['statusText'] = response.statusText returnData['headers'] = response.headers } catch (error) { throw handleErrorMessage(error) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: HTTP } ================================================ FILE: packages/components/nodes/Helio/Helio.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' class Helio implements INode { // properties label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number // parameters actions: INodeParams[] credentials?: INodeParams[] networks?: INodeParams[] inputParameters?: INodeParams[] constructor() { // properties this.label = 'Helio' this.name = 'helio' this.icon = 'helio.png' this.type = 'action' this.category = 'Payment' this.version = 1.0 this.description = 'Execute Helio API integration' this.incoming = 1 this.outgoing = 1 // parameter this.actions = [ { label: 'Operation', name: 'operation', type: 'options', options: [ { label: 'Get All Transactions', name: 'listTransactions' } ], default: 'listTransactions' } ] as INodeParams[] this.networks = [ { label: 'Network', name: 'network', type: 'options', description: 'Network to execute API: Test or Prod', options: [ { label: 'TEST', name: 'test', description: 'Test network: https://dev.hel.io/' }, { label: 'PROD', name: 'prod', description: 'Prod network: https://hel.io/' } ], default: 'test' } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Helio API Key', name: 'helioApi' } ], default: 'helioApi' } ] as INodeParams[] } async run(nodeData: INodeData): Promise { const { actions, networks, credentials } = nodeData if (actions === undefined || networks === undefined) { throw new Error('Required data missing') } if (credentials === undefined) { throw new Error('Missing credentials') } const network = networks.network as string const operation = actions.operation as string const apiKey = credentials.apiKey as string const secretKey = credentials.secretKey as string const baseUrl = network === 'test' ? 'https://dev.api.hel.io/v1' : 'https://api.hel.io/v1' const returnData: ICommonObject[] = [] let responseData: any let method: Method = 'GET' let url = '' if (operation === 'listTransactions') { url = `${baseUrl}/export/payments?apiKey=${apiKey}` } try { const axiosConfig: AxiosRequestConfig = { method, url, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${secretKey}` } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: Helio } ================================================ FILE: packages/components/nodes/Helio/HelioWebhook.ts ================================================ import { ICommonObject, INode, INodeData, INodeParams, IWebhookNodeExecutionData, NodeType } from '../../src/Interface' import { returnWebhookNodeExecutionData } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' class HelioWebhook implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions: INodeParams[] networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Helio Webhook' this.name = 'helioWebhook' this.icon = 'helio.png' this.type = 'webhook' this.category = 'Payment' this.version = 1.0 this.description = 'Start workflow whenever Helio webhook event happened' this.incoming = 0 this.outgoing = 1 this.networks = [ { label: 'Network', name: 'network', type: 'options', description: 'Network to execute API: Test or Prod', options: [ { label: 'TEST', name: 'test', description: 'Test network: https://dev.hel.io/' }, { label: 'PROD', name: 'prod', description: 'Prod network: https://hel.io/' } ], default: 'test' } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Helio API Key', name: 'helioApi' } ], default: 'helioApi' } ] as INodeParams[] this.actions = [ { label: 'Event', name: 'event', type: 'options', options: [ { label: 'New payment on Pay Link', name: 'CREATED', description: 'Triggered upon new payment on the Pay Link' }, { label: 'New subscription on Pay Stream', name: 'STARTED', description: `Triggered upon new subscription/stream started on the Pay Stream` }, { label: 'Cancellation of subscription on Pay Stream', name: 'ENDED', description: `Triggered when a subscription/stream was stopped/ended on the Pay Stream` } ] } ] as INodeParams[] this.inputParameters = [ { label: 'Pay Link Id', name: 'paylinkId', type: 'string', placeholder: '63ea24cc1ea62a8e0d272444', description: 'For example, pay link id of https://hel.io/pay/63ea24cc1ea62a8e0d272444 is 63ea24cc1ea62a8e0d272444', show: { 'actions.event': ['CREATED'] } }, { label: 'Pay Stream Id', name: 'streamId', type: 'string', placeholder: '63ea543143507a1df4f6fccf', description: 'For example, pay stream id of https://hel.io/pay/63ea543143507a1df4f6fccf is 63ea543143507a1df4f6fccf', show: { 'actions.event': ['STARTED', 'ENDED'] } } ] as INodeParams[] } webhookMethods = { async createWebhook(nodeData: INodeData, webhookFullUrl: string): Promise { // Check if webhook exists const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters const networksData = nodeData.networks const actionsData = nodeData.actions if (inputParametersData === undefined || actionsData === undefined || networksData === undefined) { throw new Error('Required data missing') } if (credentials === undefined) { throw new Error('Missing credentials') } const apiKey = credentials.apiKey as string const secretKey = credentials.secretKey as string const network = networksData.network as string const baseUrl = network === 'test' ? 'https://dev.api.hel.io/v1' : 'https://api.hel.io/v1' const paylinkId = inputParametersData.paylinkId as string const streamId = inputParametersData.streamId as string const event = actionsData.event as string const payType = paylinkId ? 'paylink' : 'stream' const payTypeId = paylinkId ? 'paylinkId' : 'streamId' const payId = paylinkId ? paylinkId : streamId const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `${baseUrl}/webhook/${payType}/transaction?apiKey=${apiKey}&${payTypeId}=${payId}`, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${secretKey}` } } try { const response = await axios(axiosConfig) const webhooks = response.data let webhookExist = false for (const webhook of webhooks) { if (webhook.events.includes(event) && webhook.targetUrl === webhookFullUrl) { // Check match pay link id if (paylinkId) { if (webhook.paylink === paylinkId) { webhookExist = true break } } // Check match pay stream id if (streamId) { if (webhook.stream === streamId) { webhookExist = true break } } } } if (!webhookExist) { const data: ICommonObject = { events: [event], targetUrl: webhookFullUrl } if (paylinkId) data.paylinkId = paylinkId if (streamId) data.streamId = streamId const axiosCreateConfig: AxiosRequestConfig = { method: 'POST' as Method, url: `${baseUrl}/webhook/${payType}/transaction?apiKey=${apiKey}`, data, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${secretKey}` } } const response = await axios(axiosCreateConfig) const createResponseData = response.data if (createResponseData && createResponseData.id) { return createResponseData.id } return } } catch (error) { return } }, async deleteWebhook(nodeData: INodeData, webhookId: string): Promise { const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters const networksData = nodeData.networks const actionsData = nodeData.actions if (inputParametersData === undefined || actionsData === undefined || networksData === undefined) { throw new Error('Required data missing') } if (credentials === undefined) { throw new Error('Missing credentials') } const apiKey = credentials.apiKey as string const secretKey = credentials.secretKey as string const network = networksData.network as string const baseUrl = network === 'test' ? 'https://dev.api.hel.io/v1' : 'https://api.hel.io/v1' const paylinkId = inputParametersData.paylinkId as string const payType = paylinkId ? 'paylink' : 'stream' const axiosConfig: AxiosRequestConfig = { method: 'DELETE' as Method, url: `${baseUrl}/webhook/${payType}/transaction/${webhookId}?apiKey=${apiKey}`, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${secretKey}` } } try { await axios(axiosConfig) } catch (error) { return false } return true } } async runWebhook(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters const req = nodeData.req if (inputParametersData === undefined) { throw new Error('Required data missing') } if (req === undefined) { throw new Error('Missing request') } //TODO: Verify webhook via signing key const returnData: ICommonObject[] = [] returnData.push({ headers: req?.headers, params: req?.params, query: req?.query, body: req?.body, rawBody: (req as any).rawBody, url: req?.url }) return returnWebhookNodeExecutionData(returnData) } } module.exports = { nodeClass: HelioWebhook } ================================================ FILE: packages/components/nodes/Helius/Helius.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' class Helius implements INode { // properties label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number // parameters actions: INodeParams[] credentials?: INodeParams[] networks?: INodeParams[] inputParameters?: INodeParams[] constructor() { // properties this.label = 'Helius' this.name = 'helius' this.icon = 'helius.png' this.type = 'action' this.category = 'Network Provider' this.version = 1.0 this.description = 'Perform Helius on-chain operations on Solana' this.incoming = 1 this.outgoing = 1 // parameter this.actions = [ { label: 'API', name: 'api', type: 'options', options: [ { label: 'Get NFT events', name: 'nft-events', description: 'Returns all NFT events given an address.' }, { label: 'Get NFT Portfolio', name: 'nfts', description: 'Returns all the NFTs that the given address currently holds.' }, { label: 'Name Lookup', name: 'names', description: 'Does a reverse lookup with the given address for Solana Naming Service domains.' }, { label: 'Get Token Balances', name: 'balances', description: 'Returns the native Solana balance (in lamports) and all token balances for a given address.' } ], default: 'balances' } ] as INodeParams[] this.credentials = [ // credentialMethod is mandatory field { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Helius API Key', name: 'heliusApi' } ], default: 'heliusApi' } ] as INodeParams[] this.inputParameters = [ { label: 'Address', name: 'address', type: 'string', optional: false } ] as INodeParams[] } async run(nodeData: INodeData): Promise { const { actions, inputParameters, credentials } = nodeData if (actions === undefined || inputParameters === undefined || credentials === undefined) { throw new Error('Required data missing') } const api = actions.api as string const apiKey = credentials.apiKey as string const address = inputParameters.address as string const apiURL = 'https://api.helius.xyz/v0/addresses' const resource = `${api}` const options = `api-key=${apiKey}` const url = `${apiURL}/${address}/${resource}?${options}` const returnData: ICommonObject[] = [] let responseData: any try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url, headers: { 'Content-Type': 'application/json' } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: Helius } ================================================ FILE: packages/components/nodes/Hubspot/Hubspot.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' class Hubspot implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions: INodeParams[] credentials?: INodeParams[] networks?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Hubspot' this.name = 'hubspot' this.icon = 'hubspot.svg' this.type = 'action' this.category = 'Communication' this.version = 2.0 this.description = 'Execute Hubspot API integration' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Operation', name: 'operation', type: 'options', options: [ { label: 'Create new contact', name: 'addContact', description: 'Create a new contact' }, { label: 'Get contact', name: 'getContact', description: 'Get a contact details' }, { label: 'Delete contact', name: 'deleteContact', description: 'Delete a contact' }, { label: 'Get list of contacts', name: 'listContacts', description: 'Get a list of contact details' } ] } ] as INodeParams[] this.credentials = [ // credentialMethod is mandatory field { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Hubspot Credentials', name: 'hubspotCredential' } ], default: 'hubspotCredential' } ] as INodeParams[] this.inputParameters = [ { label: 'Customer Email', name: 'email', type: 'string', show: { 'actions.operation': ['addContact'] } }, { label: 'Customer First Name', name: 'firstname', type: 'string', optional: true, show: { 'actions.operation': ['addContact'] } }, { label: 'Customer Last Name', name: 'lastname', type: 'string', optional: true, show: { 'actions.operation': ['addContact'] } }, { label: 'Customer Company', name: 'company', type: 'string', optional: true, show: { 'actions.operation': ['addContact'] } }, { label: 'Contact', name: 'contactId', type: 'asyncOptions', loadMethod: 'getContacts', show: { 'actions.operation': ['getContact', 'deleteContact'] } } ] as INodeParams[] } loadMethods = { async getContacts(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const credentials = nodeData.credentials const accessToken = credentials!.accessToken as string if (!accessToken) return returnData try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://api.hubapi.com/crm/v3/objects/contacts`, headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' } } const response = await axios(axiosConfig) const contacts = response.data?.results contacts.forEach((contact: any) => { const data = { label: contact.properties.email, name: contact.id } as INodeOptionsValue returnData.push(data) }) return returnData } catch (e) { return returnData } } } async run(nodeData: INodeData): Promise { const actionData = nodeData.actions const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (actionData === undefined) { throw handleErrorMessage({ message: 'Required data missing' }) } if (credentials === undefined) { throw new Error('Missing credential!') } const accessToken = credentials.accessToken as string const operation = actionData.operation as string async function makeApiCall(method: string, url: string, operation: string, body?: ICommonObject): Promise { const axiosConfig: AxiosRequestConfig = { method: method as Method, url, headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' } } if (method === 'post' && body) axiosConfig.data = body let responseData: any[] = [] try { const response = await axios(axiosConfig) if (response?.data) { responseData.push(response?.data) } } catch (err) { if (operation === 'addContact' && err.response.data.message.includes('Contact already exists')) { responseData.push(err.response.data) // dont throw error } else throw handleErrorMessage(err) } return responseData } let returnData: ICommonObject[] = [] let url = `https://api.hubapi.com/crm/v3/objects` const contactId = inputParametersData?.contactId as string if (operation === 'listContacts') { returnData = await makeApiCall('get', `${url}/contacts`, operation) } else if (operation === 'getContact') { returnData = await makeApiCall('get', `${url}/contacts/${contactId}`, operation) } else if (operation === 'deleteContact') { returnData = await makeApiCall('delete', `${url}/contacts/${contactId}`, operation) } else if (operation === 'addContact') { const email = inputParametersData?.email as string const firstname = inputParametersData?.firstname as string const lastname = inputParametersData?.lastname as string const company = inputParametersData?.company as string const body = { properties: { email } } as any if (firstname) body.properties.firstname = firstname if (lastname) body.properties.lastname = lastname if (company) body.properties.company = company const createContactResponse = await makeApiCall('post', `${url}/contacts`, operation, body) if (createContactResponse[0].message && createContactResponse[0].message.includes('Contact already exists')) { const listContactsResponse = await makeApiCall('get', `${url}/contacts`, operation) const contacts = listContactsResponse[0].results || [] const findContact = contacts.find((contact: any) => contact.properties.email === email) if (findContact) return returnNodeExecutionData(findContact) } returnData = createContactResponse } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: Hubspot } ================================================ FILE: packages/components/nodes/HuggingFace/HuggingFace.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' import FormData from 'form-data' class HuggingFace implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Hugging Face' this.name = 'huggingFace' this.icon = 'huggingface.png' this.type = 'action' this.category = 'Artificial Intelligence' this.version = 1.0 this.description = 'Execute HuggingFace Inference API' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Category', name: 'category', type: 'options', options: [ { label: 'Image Classification', name: 'image-classification' }, { label: 'Feature Extraction', name: 'feature-extraction' }, { label: 'Object Detection', name: 'object-detection' }, { label: 'Text Classification', name: 'text-classification' } ] }, { label: 'Model', name: 'model', type: 'asyncOptions', loadMethod: 'listModels' }, { label: 'Image File', name: 'imageFile', type: 'file', show: { 'actions.category': ['image-classification', 'object-detection'] } }, { label: 'Input Text', name: 'inputText', type: 'string', rows: 5, show: { 'actions.category': ['feature-extraction', 'text-classification'] } }, { label: 'Inference Endpoint', name: 'inferenceURL', type: 'string', optional: true, description: 'If this is not specify, the default free URL with limited usage will be used' } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'HuggingFace Access Token', name: 'huggingFaceAccessToken' } ], default: 'huggingFaceAccessToken' } ] as INodeParams[] } loadMethods = { async listModels(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const actionsData = nodeData.actions if (actionsData === undefined) return returnData const category = actionsData.category as string try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://huggingface.co/api/models?filter=${category}&sort=downloads&direction=-1&limit=30`, headers: { 'Content-Type': 'application/json; charset=utf-8' } } const response = await axios(axiosConfig) const models = response.data for (let i = 0; i < models.length; i += 1) { const splitedModel = models[i].id.split('/') const modelName = splitedModel.length > 1 ? splitedModel[1] : splitedModel[0] const modelAuthor = splitedModel.length > 1 ? splitedModel[0] : 'HuggingFace' const data = { label: modelName, name: models[i].id, description: `${modelAuthor} (${models[i].downloads} downloads)` } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } } } async run(nodeData: INodeData): Promise { const credentials = nodeData.credentials const actionsData = nodeData.actions if (actionsData === undefined) { throw new Error('Required data missing') } if (credentials === undefined) { throw new Error('Missing credential') } let responseData: any const returnData: ICommonObject[] = [] const model = actionsData.model as string const imageFile = actionsData.imageFile as string const inputText = actionsData.inputText as string const inferenceURL = actionsData.inferenceURL as string const url = inferenceURL || `https://api-inference.huggingface.co/models/${model}` if (inputText) { const data = { inputs: inputText } as any try { const axiosConfig: AxiosRequestConfig = { method: 'POST' as Method, url, data, headers: { 'Content-Type': 'application/json; charset=utf-8', Authorization: `Bearer ${credentials!.accessToken}` } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } } else if (imageFile) { const splitDataURI = imageFile.split(',') const filename = (splitDataURI.pop() || 'filename:').split(':')[1] const bf = Buffer.from(splitDataURI.pop() || '', 'base64') const formData = new FormData() formData.append('file', bf, filename) try { const axiosConfig: AxiosRequestConfig = { method: 'POST' as Method, url, data: bf, headers: { Authorization: `Bearer ${credentials!.accessToken}` } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: HuggingFace } ================================================ FILE: packages/components/nodes/IfElse/IfElse.ts ================================================ import { CommonType, ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { returnNodeExecutionData } from '../../src/utils' interface ICondition { type: string value1: CommonType operation: string value2: CommonType } class IfElse implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number inputParameters?: INodeParams[] constructor() { this.label = 'If Else' this.name = 'ifElse' this.icon = 'ifelse.svg' this.version = 1.0 this.type = 'action' this.category = 'Utilities' this.description = 'Split flows according to conditions set' this.incoming = 1 this.outgoing = 2 this.inputParameters = [ { label: 'Mode', name: 'mode', type: 'options', options: [ { label: 'AND', name: 'and', description: 'When all conditions are met' }, { label: 'OR', name: 'or', description: 'When any of the conditions is met' } ], default: 'or', description: 'Type of conditions' }, { label: 'Conditions', name: 'conditions', type: 'array', description: 'Values to compare', array: [ { label: 'Type', name: 'type', type: 'options', options: [ { label: 'String', name: 'string' }, { label: 'Number', name: 'number' }, { label: 'Boolean', name: 'boolean' } ], default: 'string' }, /////////////////////////////////////// STRING //////////////////////////////////////// { label: 'Value 1', name: 'value1', type: 'string', default: '', description: 'First value to be compared with', show: { 'inputParameters.conditions[$index].type': ['string'] } }, { label: 'Operation', name: 'operation', type: 'options', options: [ { label: 'Contains', name: 'contains' }, { label: 'Ends With', name: 'endsWith' }, { label: 'Equal', name: 'equal' }, { label: 'Not Contains', name: 'notContains' }, { label: 'Not Equal', name: 'notEqual' }, { label: 'Regex', name: 'regex' }, { label: 'Starts With', name: 'startsWith' }, { label: 'Is Empty', name: 'isEmpty' }, { label: 'Not Empty', name: 'notEmpty' } ], default: 'equal', description: 'Type of operation', show: { 'inputParameters.conditions[$index].type': ['string'] } }, { label: 'Value 2', name: 'value2', type: 'string', default: '', description: 'Second value to be compared with', show: { 'inputParameters.conditions[$index].type': ['string'] }, hide: { 'inputParameters.conditions[$index].operation': ['isEmpty', 'notEmpty'] } }, /////////////////////////////////////// NUMBER //////////////////////////////////////// { label: 'Value 1', name: 'value1', type: 'number', default: '', description: 'First value to be compared with', show: { 'inputParameters.conditions[$index].type': ['number'] } }, { label: 'Operation', name: 'operation', type: 'options', options: [ { label: 'Smaller', name: 'smaller' }, { label: 'Smaller Equal', name: 'smallerEqual' }, { label: 'Equal', name: 'equal' }, { label: 'Not Equal', name: 'notEqual' }, { label: 'Larger', name: 'larger' }, { label: 'Larger Equal', name: 'largerEqual' }, { label: 'Is Empty', name: 'isEmpty' }, { label: 'Not Empty', name: 'notEmpty' } ], default: 'equal', description: 'Type of operation', show: { 'inputParameters.conditions[$index].type': ['number'] } }, { label: 'Value 2', name: 'value2', type: 'number', default: 0, description: 'Second value to be compared with', show: { 'inputParameters.conditions[$index].type': ['number'] }, hide: { 'inputParameters.conditions[$index].operation': ['isEmpty', 'notEmpty'] } }, /////////////////////////////////////// BOOLEAN //////////////////////////////////////// { label: 'Value 1', name: 'value1', type: 'boolean', default: false, description: 'First value to be compared with', show: { 'inputParameters.conditions[$index].type': ['boolean'] } }, { label: 'Operation', name: 'operation', type: 'options', options: [ { label: 'Equal', name: 'equal' }, { label: 'Not Equal', name: 'notEqual' } ], default: 'equal', description: 'Type of operation', show: { 'inputParameters.conditions[$index].type': ['boolean'] } }, { label: 'Value 2', name: 'value2', type: 'boolean', default: false, description: 'Second value to be compared with', show: { 'inputParameters.conditions[$index].type': ['boolean'] } } ] } ] } async run(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters if (inputParametersData === undefined) { throw new Error('Required data missing') } let returnDataTrue: ICommonObject = {} let returnDataFalse: ICommonObject = {} const mode = inputParametersData.mode as string const compareOperationFunctions: { [key: string]: (value1: CommonType, value2: CommonType) => boolean } = { contains: (value1: CommonType, value2: CommonType) => (value1 || '').toString().includes((value2 || '').toString()), notContains: (value1: CommonType, value2: CommonType) => !(value1 || '').toString().includes((value2 || '').toString()), endsWith: (value1: CommonType, value2: CommonType) => (value1 as string).endsWith(value2 as string), equal: (value1: CommonType, value2: CommonType) => value1 === value2, notEqual: (value1: CommonType, value2: CommonType) => value1 !== value2, larger: (value1: CommonType, value2: CommonType) => (Number(value1) || 0) > (Number(value2) || 0), largerEqual: (value1: CommonType, value2: CommonType) => (Number(value1) || 0) >= (Number(value2) || 0), smaller: (value1: CommonType, value2: CommonType) => (Number(value1) || 0) < (Number(value2) || 0), smallerEqual: (value1: CommonType, value2: CommonType) => (Number(value1) || 0) <= (Number(value2) || 0), startsWith: (value1: CommonType, value2: CommonType) => (value1 as string).startsWith(value2 as string), isEmpty: (value1: CommonType) => [undefined, null, ''].includes(value1 as string), notEmpty: (value1: CommonType) => ![undefined, null, ''].includes(value1 as string) } const conditions = inputParametersData.conditions as unknown as ICondition[] let score = 0 const metConditions = [] const unmetConditions = [] for (const condition of conditions) { const value1 = condition.value1 const value2 = condition.value2 const operation = condition.operation const compareOperationResult = compareOperationFunctions[operation](value1, value2) if (compareOperationResult) { score += 1 metConditions.push({ value1, operation, value2 }) } else { unmetConditions.push({ value1, operation, value2 }) } } const data = { mode, metConditions, unmetConditions } if (mode === 'or') { if (score > 0) returnDataTrue = data else returnDataFalse = data } else if (mode === 'and') { if (score === conditions.length) returnDataTrue = data else returnDataFalse = data } const returnData = [returnDataTrue, returnDataFalse] return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: IfElse } ================================================ FILE: packages/components/nodes/ImageEditor/ImageEditor.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import Jimp from 'jimp' import { promisify } from 'util' class ImageEditor implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'ImageEditor' this.name = 'imageEditor' this.icon = 'image-editor.svg' this.type = 'action' this.category = 'Utilities' this.version = 1.0 this.description = 'Edit image with different manipulation methods' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Selection Method', name: 'method', type: 'options', options: [ { label: 'Crop', name: 'crop', description: 'Crop image' }, { label: 'Blur', name: 'blur', description: 'Quickly blur an image' }, { label: 'Gaussian', name: 'gaussian', description: 'Hardcore blur' }, { label: 'Invert', name: 'invert', description: 'Invert an images colors' }, { label: 'Resize', name: 'resize', description: 'Resize an image' }, { label: 'Cover', name: 'cover', description: 'Scale the image so the given width and height keeping the aspect ratio' }, { label: 'Rotate', name: 'rotate', description: 'Rotate an image' }, { label: 'Normalize', name: 'normalize', description: 'Normalize the colors in an image' }, { label: 'Dither', name: 'dither', description: 'Apply a dither effect to an image' }, { label: 'Scale', name: 'scale', description: 'Uniformly scales the image by a factor' } ] }, { label: 'Image Raw Data (Base64)', name: 'rawData', type: 'string', placeholder: 'data:image/png;base64,' } ] this.inputParameters = [ /** * Blur or Gaussian */ { label: 'Blur Pixel Radius', name: 'blurPixel', type: 'number', default: 5, show: { 'actions.method': ['blur', 'gaussian'] }, description: 'The pixel radius of the blur' }, /** * Crop */ { label: 'Width', name: 'width', type: 'number', default: 500, show: { 'actions.method': ['crop'] }, description: 'Crop width' }, { label: 'Height', name: 'height', type: 'number', default: 500, show: { 'actions.method': ['crop'] }, description: 'Crop height' }, { label: 'Position X', name: 'positionX', type: 'number', default: 10, show: { 'actions.method': ['crop'] }, description: 'X (horizontal) position to crop from' }, { label: 'Position Y', name: 'positionY', type: 'number', default: 10, show: { 'actions.method': ['crop'] }, description: 'Y (vertical) position to crop from' }, /** * Resize or Cover */ { label: 'Width', name: 'width', type: 'number', default: 500, show: { 'actions.method': ['resize', 'cover'] }, description: 'Resize width' }, { label: 'Height', name: 'height', type: 'number', default: 500, show: { 'actions.method': ['resize', 'cover'] }, description: 'Resize height' }, /** * Rotate */ { label: 'Rotation Degree', name: 'degree', type: 'number', default: 90, show: { 'actions.method': ['rotate'] } }, /** * Scale */ { label: 'Scale Factor', name: 'factor', type: 'number', default: 2, show: { 'actions.method': ['scale'] } } ] as INodeParams[] } async run(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters const actionsData = nodeData.actions if (inputParametersData === undefined || actionsData === undefined) { throw new Error('Required data missing') } const returnData: ICommonObject = {} const rawData = actionsData.rawData as string const method = actionsData.method as string try { const imageData = rawData.split(',').pop() || '' const image = await Jimp.read(Buffer.from(imageData, 'base64')) if (method === 'crop') { const positionX = parseInt(inputParametersData.positionX as string, 10) const positionY = parseInt(inputParametersData.positionY as string, 10) const height = parseInt(inputParametersData.height as string, 10) const width = parseInt(inputParametersData.width as string, 10) image.crop(positionX, positionY, width, height, (err) => { if (err) throw handleErrorMessage(err) }) } else if (method === 'blur' || method === 'gaussian') { const blurPixel = parseInt(inputParametersData.blurPixel as string, 10) if (method === 'blur') { image.blur(blurPixel, (err) => { if (err) throw handleErrorMessage(err) }) } else if (method === 'gaussian') { image.gaussian(blurPixel, (err) => { if (err) throw handleErrorMessage(err) }) } } else if (method === 'invert') { image.invert((err) => { if (err) throw handleErrorMessage(err) }) } else if (method === 'resize' || method === 'cover') { const height = parseInt(inputParametersData.height as string, 10) const width = parseInt(inputParametersData.width as string, 10) if (method === 'resize') { image.resize(width, height, (err) => { if (err) throw handleErrorMessage(err) }) } else if (method === 'cover') { image.cover(width, height, (err) => { if (err) throw handleErrorMessage(err) }) } } else if (method === 'rotate') { const degree = parseInt(inputParametersData.degree as string, 10) image.rotate(degree, (err) => { if (err) throw handleErrorMessage(err) }) } else if (method === 'normalize') { image.normalize((err) => { if (err) throw handleErrorMessage(err) }) } else if (method === 'dither') { image.dither565((err) => { if (err) throw handleErrorMessage(err) }) } else if (method === 'scale') { const factor = parseInt(inputParametersData.factor as string, 10) image.scale(factor, (err) => { if (err) throw handleErrorMessage(err) }) } const toBase64 = promisify(image.getBase64).bind(image) const mimeType = rawData.split(',')[0].split(';')[0].split(':')[1] const finalImage = await toBase64(mimeType) const attachment = { contentType: mimeType, content: finalImage } returnData['data'] = finalImage returnData.attachments = [attachment] } catch (error) { throw handleErrorMessage(error) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: ImageEditor } ================================================ FILE: packages/components/nodes/Infura/Infura.ts ================================================ import { IAttachment, ICommonObject, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' import { infuraHTTPAPIs, ETHNetworks, PolygonNetworks, OptimismNetworks, ArbitrumNetworks, NETWORK, AvalancheNetworks } from '../../src/ChainNetwork' import { ethOperations, IETHOperation, operationCategoryMapping, polygonOperations } from '../../src/ETHOperations' import axios, { AxiosRequestConfig, AxiosRequestHeaders, Method } from 'axios' import FormData from 'form-data' import { IPFSOperationsOptions, argParams, fileParams, catParams, dagParams, getParams, objectParams, pinParams } from './extendedOperation' class Infura implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions: INodeParams[] credentials?: INodeParams[] networks?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Infura' this.name = 'infura' this.icon = 'infura.svg' this.type = 'action' this.category = 'Network Provider' this.version = 1.1 this.description = 'Perform Infura onchain operations' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'API', name: 'api', type: 'options', options: [ { label: 'Chain API', name: 'chainAPI', description: 'API for fetching standard onchain data using Infura supported calls.' }, { label: 'IPFS API', name: 'ipfsAPI', description: 'API for interacting with IPFS, a distributed, peer-to-peer (p2p) storage network used for storing and accessing files, websites, applications, and data.' } ], default: 'chainAPI' } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Infura API Key', name: 'infuraApi' } ], default: 'infuraApi' } ] as INodeParams[] this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...ETHNetworks, ...PolygonNetworks, ...ArbitrumNetworks, ...OptimismNetworks, ...AvalancheNetworks], show: { 'actions.api': ['chainAPI'] } } ] as INodeParams[] this.inputParameters = [ { label: 'Chain Category', name: 'chainCategory', type: 'options', options: [ { label: 'Retrieving Blocks', name: 'retrievingBlocks', description: 'Retrieve onchain blocks data' }, { label: 'EVM/Smart Contract Execution', name: 'evmExecution', description: 'Execute or submit transaction onto blockchain' }, { label: 'Reading Transactions', name: 'readingTransactions', description: 'Read onchain transactions data' }, { label: 'Account Information', name: 'accountInformation', description: 'Retrieve onchain account information' }, { label: 'Event Logs', name: 'eventLogs', description: 'Fetch onchain logs' }, { label: 'Chain Information', name: 'chainInformation', description: 'Get general selected blockchain information' }, { label: 'Retrieving Uncles', name: 'retrievingUncles', description: 'Retrieve onchain uncles blocks data' }, { label: 'Filters', name: 'filters', description: 'Get block filters and logs, or create new filter' } ], show: { 'actions.api': ['chainAPI'] }, default: 'retrievingBlocks' }, { label: 'Operation', name: 'operation', type: 'asyncOptions', loadMethod: 'getOperations' }, ...fileParams, ...argParams, ...catParams, ...dagParams, ...getParams, ...objectParams, ...pinParams, { label: 'Parameters', name: 'parameters', type: 'json', placeholder: '["param1", "param2"]', optional: true, description: 'Operation parameters in array. Ex: ["param1", "param2"]', show: { 'actions.api': ['chainAPI'] } } ] as INodeParams[] } loadMethods = { async getOperations(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const actionData = nodeData.actions const networksData = nodeData.networks const inputParametersData = nodeData.inputParameters if (actionData === undefined || networksData === undefined || inputParametersData === undefined) { return returnData } const api = actionData.api as string if (api === 'chainAPI') { const network = networksData.network as NETWORK let totalOperations: IETHOperation[] = [] const chainCategory = inputParametersData.chainCategory as string const filteredOperations = ethOperations.filter( (op: IETHOperation) => Object.prototype.hasOwnProperty.call(op.providerNetworks, 'infura') && op.providerNetworks['infura'].includes(network) && op.parentGroup === operationCategoryMapping[chainCategory] ) if (network === NETWORK.MATIC || network === NETWORK.MATIC_MUMBAI) { totalOperations = [...polygonOperations, ...filteredOperations] } else { totalOperations = filteredOperations } for (const op of totalOperations) { returnData.push({ label: op.name, name: op.value, parentGroup: op.parentGroup, description: op.description, inputParameters: op.inputParameters, exampleParameters: op.exampleParameters, exampleResponse: op.exampleResponse }) } return returnData } else if (api === 'ipfsAPI') { return IPFSOperationsOptions } else { return returnData } } } async run(nodeData: INodeData): Promise { const actionData = nodeData.actions const networksData = nodeData.networks const inputParametersData = nodeData.inputParameters const credentials = nodeData.credentials if (actionData === undefined || inputParametersData === undefined || credentials === undefined || networksData === undefined) { throw new Error('Required data missing') } // GET api const api = actionData.api as string if (api === 'chainAPI') { // GET network const network = networksData.network as NETWORK // GET credentials const apiKey = credentials.apiKey as string // GET operation const operation = inputParametersData.operation as string const uri = infuraHTTPAPIs[network] + apiKey let responseData: any // tslint:disable-line: no-any let bodyParameters: any[] = [] // tslint:disable-line: no-any const returnData: ICommonObject[] = [] const parameters = inputParametersData.parameters as string if (parameters) { try { bodyParameters = JSON.parse(parameters.replace(/\s/g, '')) } catch (error) { throw handleErrorMessage(error) } } try { let totalOperations: IETHOperation[] = [] if (api === 'chainAPI') totalOperations = [...polygonOperations, ...ethOperations] const result = totalOperations.find((obj) => { return obj.value === operation }) if (result === undefined) throw new Error('Invalid Operation') const requestBody = JSON.parse(JSON.stringify(result.body)) const bodyParams = requestBody.params requestBody.params = Array.isArray(bodyParameters) ? bodyParameters.concat(bodyParams) : bodyParameters const axiosConfig: AxiosRequestConfig = { method: result.method as Method, url: uri, data: requestBody, headers: { 'Content-Type': 'application/json' } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) returnData.push(...responseData) else returnData.push(responseData) return returnNodeExecutionData(returnData) } else if (api === 'ipfsAPI') { // GET credentials const apiKey = credentials.apiKey as string const secretKey = credentials.secretKey as string // GET operation const operation = inputParametersData.operation as string let responseData: any // tslint:disable-line: no-any const returnData: ICommonObject[] = [] const apiUrl = 'https://ipfs.infura.io:5001/api' let url = '' const queryParameters: ICommonObject = {} let queryBody: any = {} let method: Method = 'POST' const headers: AxiosRequestHeaders = { 'Content-Type': 'application/json', Authorization: Buffer.from(`${apiKey}:${secretKey}`).toString('base64') } if (operation === 'block_get' || operation === 'block_stat') { const arg = inputParametersData.arg as string queryParameters['arg'] = arg method = 'POST' let endpoint = '' if (operation === 'block_stat') { endpoint = 'block/stat' } else if (operation === 'block_get') { endpoint = 'block/get' } url = `${apiUrl}/v0/${endpoint}` } else if (operation === 'add' || operation === 'block_put' || operation === 'pin_add') { const fileBase64 = inputParametersData.file as string const splitDataURI = fileBase64.split(',') const filename = (splitDataURI.pop() || 'filename:').split(':')[1] const bf = Buffer.from(splitDataURI.pop() || '', 'base64') const formData = new FormData() formData.append('file', bf, filename) method = 'POST' let endpoint = '' if (operation === 'add') { endpoint = 'add' } else if (operation === 'block_put') { endpoint = 'block/put' } else if (operation === 'pin_add') { endpoint = 'pin/add' const arg = inputParametersData.arg as string queryParameters['arg'] = arg } url = `${apiUrl}/v0/${endpoint}` headers['Content-Type'] = 'multipart/form-data; boundary=' + formData.getBoundary() queryBody = formData } else if (operation === 'dag_get' || operation === 'dag_resolve') { const arg = inputParametersData.arg as string const outputCodec = inputParametersData['output-codec'] as string queryParameters['arg'] = arg if (outputCodec) queryParameters['output-codec'] = outputCodec method = 'POST' let endpoint = '' if (operation === 'dag_get') { endpoint = 'dag/get' } else if (operation === 'dag_resolve') { endpoint = 'dag/resolve' } url = `${apiUrl}/v0/${endpoint}` } else if (operation === 'dag_put') { const storeCodec = inputParametersData['store-codec'] as string const inputCodec = inputParametersData['input-codec'] as string const pin = inputParametersData.pin as boolean const hash = inputParametersData.hash as string if (storeCodec) queryParameters['store-codec'] = storeCodec if (inputCodec) queryParameters['input-codec'] = inputCodec if (pin) queryParameters.pin = pin if (hash) queryParameters.hash = hash const fileBase64 = inputParametersData.file as string const splitDataURI = fileBase64.split(',') const filename = (splitDataURI.pop() || 'filename:').split(':')[1] const bf = Buffer.from(splitDataURI.pop() || '', 'base64') const formData = new FormData() formData.append('file', bf, filename) method = 'POST' url = `${apiUrl}/v0/dag/put` headers['Content-Type'] = 'multipart/form-data; boundary=' + formData.getBoundary() queryBody = formData } else if (operation === 'object_data' || operation === 'object_stat' || operation === 'object_get') { /* else if (operation === 'get') { const arg = inputParametersData.arg as string; const output = inputParametersData.output as string; const archive = inputParametersData.archive as boolean; const compress = inputParametersData.compress as boolean; const compressionLevel = inputParametersData['compression-level'] as number; queryParameters['arg'] = arg; if (output) queryParameters['output'] = output; if (archive) queryParameters['archive'] = archive; if (compress) queryParameters['compress'] = compress; if (compressionLevel) queryParameters['compressionLevel'] = compressionLevel; method = 'POST'; url = `${apiUrl}/v0/get`; } */ const arg = inputParametersData.arg as string queryParameters['arg'] = arg method = 'POST' let endpoint = '' if (operation === 'object_data') { endpoint = 'object/data' } else if (operation === 'object_get') { endpoint = 'object/get' } else if (operation === 'object_stat') { endpoint = 'object/stat' } url = `${apiUrl}/v0/${endpoint}` } else if (operation === 'object_put') { const inputenc = inputParametersData.inputenc as string const datafieldenc = inputParametersData.datafieldenc as string const pin = inputParametersData.pin as boolean if (inputenc) queryParameters.inputenc = inputenc if (datafieldenc) queryParameters.datafieldenc = datafieldenc if (pin) queryParameters.pin = pin const fileBase64 = inputParametersData.file as string const splitDataURI = fileBase64.split(',') const filename = (splitDataURI.pop() || 'filename:').split(':')[1] const bf = Buffer.from(splitDataURI.pop() || '', 'base64') const formData = new FormData() formData.append('file', bf, filename) method = 'POST' url = `${apiUrl}/v0/object/put` headers['Content-Type'] = 'multipart/form-data; boundary=' + formData.getBoundary() queryBody = formData } else if (operation === 'pin_ls' || operation === 'pin_rm') { const arg = inputParametersData.arg as string const type = inputParametersData.type as string queryParameters['arg'] = arg if (type) queryParameters['type'] = type method = 'POST' let endpoint = '' if (operation === 'pin_ls') { endpoint = 'pin/ls' } else if (operation === 'pin_rm') { endpoint = 'pin/rm' } url = `${apiUrl}/v0/${endpoint}` } try { const result = IPFSOperationsOptions.find((obj) => { return obj.name === operation }) if (result === undefined) throw new Error('Invalid Operation') const axiosConfig: AxiosRequestConfig = { method, url, params: queryParameters, paramsSerializer: (params) => serializeQueryParams(params), headers, data: queryBody } if (operation === 'cat' || operation === 'get') { const arg = inputParametersData.arg as string const ipfsURL = `https://ipfs.infura.io/ipfs/${arg}` const axiosConfig: AxiosRequestConfig = { method: 'HEAD', url: ipfsURL } const ipfsResponse = await axios(axiosConfig) const mimeType = ipfsResponse.headers['content-type'] const attachment = { content: ipfsURL, contentType: mimeType } as IAttachment const returnData: any = {} returnData.ipfsURL = ipfsURL returnData.attachments = [attachment] return returnNodeExecutionData(returnData) } else { const response = await axios(axiosConfig) responseData = response.data } } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) returnData.push(...responseData) else returnData.push(responseData) return returnNodeExecutionData(returnData) } /* else if (api === 'filecoinAPI') { // GET credentials const apiKey = credentials.apiKey as string; const secretKey = credentials.secretKey as string; // GET operation const operation = inputParametersData.operation as string; let responseData: any; // tslint:disable-line: no-any const returnData: ICommonObject[] = []; const url = `https://${apiKey}:${secretKey}@filecoin.infura.io`; const queryParameters: ICommonObject = {}; const queryBody: any = { "id": 0, "jsonrpc": "2.0", "method": "", "params": [] }; const method: Method = 'POST'; const headers: AxiosRequestHeaders = { 'Content-Type': 'application/json', 'Authorization': Buffer.from(`${apiKey}:${secretKey}`).toString('base64') }; if (operation === 'ChainHead') { queryBody["method"] = `Filecoin.${operation}`; } try { const result = FilecoinOperationsOptions.find(obj => { return obj.name === operation }); if (result === undefined) throw new Error('Invalid Operation'); const axiosConfig: AxiosRequestConfig = { method, url, params: queryParameters, paramsSerializer: params => serializeQueryParams(params), headers, data: queryBody } const response = await axios(axiosConfig); responseData = response.data; } catch (error) { throw handleErrorMessage(error); } if (Array.isArray(responseData)) returnData.push(...responseData); else returnData.push(responseData); return returnNodeExecutionData(returnData); } */ return returnNodeExecutionData([]) } } module.exports = { nodeClass: Infura } ================================================ FILE: packages/components/nodes/Infura/InfuraTrigger.ts ================================================ import { INode, INodeData, INodeOptionsValue, INodeParams, IProviders, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import EventEmitter from 'events' import { ArbitrumNetworks, AvalancheNetworks, ETHNetworks, infuraWSSAPIs, NETWORK, OptimismNetworks, PolygonNetworks } from '../../src/ChainNetwork' import { subscribeOperations, unsubscribeOperations } from './subscribeOperation' import { IETHOperation } from '../../src/ETHOperations' import WebSocket from 'ws' class InfuraTrigger extends EventEmitter implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] providers: IProviders constructor() { super() this.label = 'Infura Trigger' this.name = 'infuraTrigger' this.icon = 'infura.svg' this.type = 'trigger' this.category = 'Network Provider' this.version = 1.0 this.description = 'Start workflow whenever subscribed event happened' this.incoming = 0 this.outgoing = 1 this.providers = {} this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...ETHNetworks, ...PolygonNetworks, ...ArbitrumNetworks, ...OptimismNetworks, ...AvalancheNetworks] } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Infura API Key', name: 'infuraApi' } ], default: 'infuraApi' } ] as INodeParams[] this.inputParameters = [ { label: 'Subscribe Operation', name: 'subscribeOperation', type: 'asyncOptions', loadMethod: 'getSubscribeOperations' }, { label: 'Parameters', name: 'parameters', type: 'json', placeholder: `[ "param1", "param2" ]`, optional: true, description: 'Operation parameters in array. Ex: ["param1", "param2"]' }, { label: 'Unsubscribe Operation', name: 'unsubscribeOperation', type: 'asyncOptions', loadMethod: 'getUnsubscribeOperations' } ] as INodeParams[] } loadMethods = { async getSubscribeOperations(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) { return returnData } const network = networksData.network as NETWORK const totalOperations = subscribeOperations const filteredOperations = totalOperations.filter( (op: IETHOperation) => Object.prototype.hasOwnProperty.call(op.providerNetworks, 'infura') && op.providerNetworks['infura'].includes(network) ) for (const op of filteredOperations) { returnData.push({ label: op.name, name: op.value, parentGroup: op.parentGroup, description: op.description, inputParameters: op.inputParameters, exampleParameters: op.exampleParameters, exampleResponse: op.exampleResponse }) } return returnData }, async getUnsubscribeOperations(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) { return returnData } const network = networksData.network as NETWORK const totalOperations = unsubscribeOperations const filteredOperations = totalOperations.filter( (op: IETHOperation) => Object.prototype.hasOwnProperty.call(op.providerNetworks, 'infura') && op.providerNetworks['infura'].includes(network) ) for (const op of filteredOperations) { returnData.push({ label: op.name, name: op.value, parentGroup: op.parentGroup, description: op.description, inputParameters: op.inputParameters, exampleParameters: op.exampleParameters, exampleResponse: op.exampleResponse }) } return returnData } } async runTrigger(nodeData: INodeData): Promise { const networksData = nodeData.networks const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (networksData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } if (credentials === undefined) { throw new Error('Missing credentials') } // GET network const network = networksData.network as NETWORK // GET credentials const apiKey = credentials.apiKey as string const wssProvider = `${infuraWSSAPIs[network]}${apiKey}` // GET subscribeOperation const subscribeOperation = inputParametersData.subscribeOperation as string // GET parameters let bodyParameters: any const parameters = inputParametersData.parameters as string if (parameters) { try { bodyParameters = JSON.parse(parameters.replace(/\s/g, '')) } catch (error) { throw handleErrorMessage(error) } } const emitEventKey = nodeData.emitEventKey as string const result = subscribeOperations.find((obj) => { return obj.value === subscribeOperation }) if (result === undefined) throw new Error('Invalid Operation') const requestBody = result.body requestBody.params = bodyParameters const ws = new WebSocket(wssProvider) ws.on('open', function open() { ws.send(JSON.stringify(requestBody)) }) let subscriptionID = '' ws.on('message', (data) => { const messageData = JSON.parse(data as any) if (messageData.method) { this.emit(emitEventKey, returnNodeExecutionData(messageData)) } else { subscriptionID = messageData.result this.providers[emitEventKey] = { provider: ws, filter: subscriptionID } } }) } async removeTrigger(nodeData: INodeData): Promise { const emitEventKey = nodeData.emitEventKey as string const inputParametersData = nodeData.inputParameters if (inputParametersData === undefined) { throw new Error('Required data missing') } if (Object.prototype.hasOwnProperty.call(this.providers, emitEventKey)) { const provider: WebSocket = this.providers[emitEventKey].provider const subscriptionID = this.providers[emitEventKey].filter const result = unsubscribeOperations.find((obj) => { return obj.value === (inputParametersData.unsubscribeOperation as string) }) if (result === undefined) throw new Error('Invalid Operation') const requestBody = result.body requestBody.params = [subscriptionID] provider.send(JSON.stringify(requestBody)) provider.close(1000) this.removeAllListeners(emitEventKey) } } } module.exports = { nodeClass: InfuraTrigger } ================================================ FILE: packages/components/nodes/Infura/extendedOperation.ts ================================================ import { INodeOptionsValue, INodeParams } from '../../src' export const IPFSOperationsOptions = [ { label: 'add', name: 'add', description: 'Add a file or directory to IPFS.' }, { label: 'get', name: 'get', description: 'Get IPFS objects.' }, { label: 'block_get', name: 'block_get', description: 'Get a raw IPFS block.' }, { label: 'block_put', name: 'block_put', description: 'Store input as an IPFS block.' }, { label: 'block_stat', name: 'block_stat', description: 'Print information of a raw IPFS block.' }, { label: 'cat', name: 'cat', description: 'Show IPFS object data. Same as get.' }, { label: 'dag_get', name: 'dag_get', description: 'Get a dag node from IPFS.' }, { label: 'dag_put', name: 'dag_put', description: 'Add a dag node to IPFS.' }, { label: 'dag_resolve', name: 'dag_resolve', description: 'Resolve IPLD block.' }, { label: 'object_data', name: 'object_data', description: 'Output the raw bytes of an IPFS object.' }, { label: 'object_get', name: 'object_get', description: 'Get and serialize the DAG node named by key.' }, { label: 'object_put', name: 'object_put', description: 'Store input as a DAG object, print its key.' }, { label: 'object_stat', name: 'object_stat', description: 'Get stats for the DAG node named by key.' }, { label: 'pin_add', name: 'pin_add', description: 'Pin objects to local storage.' }, { label: 'pin_ls', name: 'pin_ls', description: 'Lists objects pinned to local storage.' }, { label: 'pin_rm', name: 'pin_rm', description: 'Remove pinned objects from local storage.' } ] as INodeOptionsValue[] export const fileParams = [ { label: 'File', name: 'file', type: 'file', description: 'The path to a file to be added to IPFS.', show: { 'inputParameters.operation': ['add', 'block_put', 'dag_put', 'pin_add'] } } ] as INodeParams[] export const argParams = [ { label: 'Block Hash', name: 'arg', type: 'string', description: 'The base58 multihash of an existing block to get.', default: '', show: { 'inputParameters.operation': ['block_get', 'block_stat'] } } ] as INodeParams[] export const catParams = [ { label: 'IPFS Object Hash', name: 'arg', type: 'string', description: 'The IPFS object hash.', default: '', show: { 'inputParameters.operation': ['cat'] } } ] as INodeParams[] export const dagParams = [ { label: 'Dag Object', name: 'arg', type: 'string', description: 'The DAG object to get/resolve.', default: '', show: { 'inputParameters.operation': ['dag_get', 'dag_resolve'] } }, { label: 'Output Codec', name: 'output-codec', type: 'string', description: 'Format the object will be decoded in. Default: dag-json.', optional: true, default: '', show: { 'inputParameters.operation': ['dag_get'] } }, { label: 'Store Codec', name: 'store-codec', type: 'string', description: 'Codec that the stored object will be encoded with. Default: dag-cbor.', optional: true, default: '', show: { 'inputParameters.operation': ['dag_put'] } }, { label: 'Input Codec', name: 'input-codec', type: 'string', description: 'Codec that the input object is encoded in. Default: dag-json.', optional: true, default: '', show: { 'inputParameters.operation': ['dag_put'] } }, { label: 'Pin', name: 'pin', type: 'boolean', description: 'Pin this object when adding.', optional: true, default: true, show: { 'inputParameters.operation': ['dag_put', 'object_put'] } }, { label: 'Hash', name: 'hash', type: 'string', description: 'Hash function to use. Default: sha2-256.', optional: true, default: '', show: { 'inputParameters.operation': ['dag_put'] } } ] as INodeParams[] export const getParams = [ { label: 'IPFS Object Hash', name: 'arg', type: 'string', description: 'The IPFS object hash.', default: '', show: { 'inputParameters.operation': ['get'] } } /* { label: 'Output', name: 'output', type: 'string', description: 'The path where the output should be stored.', default: '', optional: true, show: { 'inputParameters.operation': [ 'get', ] } }, { label: 'Archive', name: 'archive', type: 'boolean', description: 'Output a TAR archive.', optional: true, default: false, show: { 'inputParameters.operation': [ 'get', ] } }, { label: 'Compress', name: 'compress', type: 'boolean', description: 'Compress the output with GZIP compression.', optional: true, default: false, show: { 'inputParameters.operation': [ 'get', ] } }, { label: 'Compress Level', name: 'compression-level', type: 'number', description: 'The level of compression (1-9). Default: “-1”', optional: true, default: '', show: { 'inputParameters.operation': [ 'get', ] } } */ ] as INodeParams[] export const objectParams = [ { label: 'Object Key', name: 'arg', type: 'string', description: 'Key of the object to retrieve, in base58-encoded multihash format.', default: '', show: { 'inputParameters.operation': ['object_data', 'object_get', 'object_stat'] } }, { label: 'File', name: 'file', type: 'file', description: 'The file to be stored as a DAG object.', show: { 'inputParameters.operation': ['object_put'] } }, { label: 'Input Encoding', name: 'inputenc', type: 'options', description: 'Encoding type of input data.', options: [ { label: 'Protobuf', name: 'protobuf' }, { label: 'Json', name: 'json' } ], default: 'json', show: { 'inputParameters.operation': ['object_put'] } }, { label: 'Data Field Encoding', name: 'datafieldenc', type: 'options', description: 'Encoding type of data field.', options: [ { label: 'Text', name: 'text' }, { label: 'Base64', name: 'base64' } ], default: 'text', show: { 'inputParameters.operation': ['object_put'] } } ] as INodeParams[] export const pinParams = [ { label: 'Object(s) Path', name: 'arg', type: 'string', description: 'Path to object(s) to be listed.', default: '', show: { 'inputParameters.operation': ['pin_ls'] } }, { label: 'Object(s) Path', name: 'arg', type: 'string', description: 'Path to object(s) to be unpinned.', default: '', show: { 'inputParameters.operation': ['pin_rm'] } }, { label: 'Object(s) Path', name: 'arg', type: 'string', description: 'Path to object(s) to be pinned.', default: '', show: { 'inputParameters.operation': ['pin_add'] } }, { label: 'Type', name: 'inputenc', type: 'options', description: 'The type of pinned keys to list.', options: [ { label: 'Direct', name: 'direct' }, { label: 'Indirect', name: 'indirect' }, { label: 'Recursive', name: 'recursive' }, { label: 'All', name: 'all' } ], default: 'all', show: { 'inputParameters.operation': ['pin_ls'] } } ] as INodeParams[] ================================================ FILE: packages/components/nodes/Infura/subscribeOperation.ts ================================================ import { NETWORK_PROVIDER } from '../../src/ChainNetwork' import { IETHOperation, infuraSupportedNetworks } from '../../src/ETHOperations' export const subsOperationsNetworks = [...infuraSupportedNetworks] export const subscribeOperations = [ { name: 'Eth Subscribe (eth_subscribe)', value: 'eth_subscribe', parentGroup: 'Subscribe', description: 'Starts a subscription to a specific event.', providerNetworks: { [NETWORK_PROVIDER.INFURA]: subsOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'eth_subscribe', params: [] }, inputParameters: `
    • subscription name: string - The type of event you want to subscribe to (i.e., newHeads, logs, pendingTransactions, newPendingTransactions). This method supports the following subscription types:
      • syncing - Indicates when the node starts or stops synchronizing. The result can either be a boolean indicating that the synchronization has started (true), finished (false) or an object with various progress indicators.
      • newPendingTransactions - Returns the hash for all transactions that are added to the pending state and are signed with a key that is available in the node. When a transaction that was previously part of the canonical chain isn't part of the new canonical chain after a reogranization its again emitted.
      • newHeads - Subscribing to this, fires a notification each time a new header is appended to the chain, including chain reorganizations. In case of a chain reorganization the subscription will emit all new headers for the new chain. Therefore the subscription can emit multiple headers on the same height.
      • logs - Returns logs that are included in new imported blocks and match the given filter criteria. In case of a chain reorganization previous sent logs that are on the old chain will be resend with the removed property set to true. Logs from transactions that ended up in the new chain are emitted. Therefore a subscription can emit logs for the same transaction multiple times.
        • address - (optional) - either an address or an array of addresses. Only logs that are created from these addresses are returned
        • topics - (optional) - only logs which match the specified topics
    • data: object - (Optional) - Arguments such as an address, multiple addresses, and topics. Note, only logs that are created from these addresses or match the specified topics will return logs.
    `, exampleParameters: `["newHeads"]` } ] as IETHOperation[] export const unsubscribeOperations = [ { name: 'Eth Unsubscribe (eth_unsubscribe)', value: 'eth_unsubscribe', parentGroup: 'Unsubscribe', description: 'Cancels an existing subscription so that no further events are sent.', providerNetworks: { [NETWORK_PROVIDER.INFURA]: subsOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'eth_unsubscribe', params: [] } } ] as IETHOperation[] ================================================ FILE: packages/components/nodes/MATICBalanceTrigger/MATICBalanceTrigger.ts ================================================ import { CronJob } from 'cron' import { BigNumber, utils } from 'ethers' import { ICronJobs, INode, INodeData, INodeParams, NodeType } from '../../src/Interface' import { returnNodeExecutionData } from '../../src/utils' import EventEmitter from 'events' import { PolygonNetworks, networkExplorers, polygonNetworkProviders, NETWORK, NETWORK_PROVIDER, getNetworkProvider, networkProviderCredentials } from '../../src/ChainNetwork' class MATICBalanceTrigger extends EventEmitter implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] cronJobs: ICronJobs constructor() { super() this.label = 'MATIC Balance Trigger' this.name = 'MATICBalanceTrigger' this.icon = 'polygon.svg' this.type = 'trigger' this.category = 'Cryptocurrency' this.version = 1.0 this.description = 'Start workflow whenever MATIC balance in wallet changes' this.incoming = 0 this.outgoing = 1 this.cronJobs = {} this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...PolygonNetworks], default: 'homestead' }, { label: 'Network Provider', name: 'networkProvider', type: 'options', options: [...polygonNetworkProviders], default: 'polygon' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] this.inputParameters = [ { label: 'Wallet Address', name: 'address', type: 'string', default: '' }, { label: 'Trigger Condition', name: 'triggerCondition', type: 'options', options: [ { label: 'When balance increased', name: 'increase' }, { label: 'When balance decreased', name: 'decrease' } ], default: 'increase' }, { label: 'Polling Time', name: 'pollTime', type: 'options', options: [ { label: 'Every 15 secs', name: '15s' }, { label: 'Every 30 secs', name: '30s' }, { label: 'Every 1 min', name: '1min' }, { label: 'Every 5 min', name: '5min' }, { label: 'Every 10 min', name: '10min' } ], default: '30s' } ] as INodeParams[] } async runTrigger(nodeData: INodeData): Promise { const networksData = nodeData.networks const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (networksData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') const emitEventKey = nodeData.emitEventKey as string const address = (inputParametersData.address as string) || '' const pollTime = (inputParametersData.pollTime as string) || '30s' const triggerCondition = (inputParametersData.triggerCondition as string) || 'increase' const cronTimes: string[] = [] if (pollTime === '15s') { cronTimes.push(`*/15 * * * * *`) } else if (pollTime === '30s') { cronTimes.push(`*/30 * * * * *`) } else if (pollTime === '1min') { cronTimes.push(`*/1 * * * *`) } else if (pollTime === '5min') { cronTimes.push(`*/5 * * * *`) } else if (pollTime === '10min') { cronTimes.push(`*/10 * * * *`) } let lastBalance: BigNumber = await provider.getBalance(address) const executeTrigger = async () => { const newBalance: BigNumber = await provider.getBalance(address) if (!newBalance.eq(lastBalance)) { if (triggerCondition === 'increase' && newBalance.gt(lastBalance)) { const balanceInMATIC = utils.formatEther(BigNumber.from(newBalance.toString())) const diffInMATIC = newBalance.sub(lastBalance) const returnItem = { newBalance: `${balanceInMATIC} MATIC`, lastBalance: `${utils.formatEther(BigNumber.from(lastBalance.toString()))} MATIC`, difference: `${utils.formatEther(BigNumber.from(diffInMATIC.toString()))} MATIC`, explorerLink: `${networkExplorers[network]}/address/${address}`, triggerCondition: 'MATIC balance increase' } lastBalance = newBalance this.emit(emitEventKey, returnNodeExecutionData(returnItem)) } else if (triggerCondition === 'decrease' && newBalance.lt(lastBalance)) { const balanceInMATIC = utils.formatEther(BigNumber.from(newBalance.toString())) const diffInMATIC = lastBalance.sub(newBalance) const returnItem = { newBalance: `${balanceInMATIC} MATIC`, lastBalance: `${utils.formatEther(BigNumber.from(lastBalance.toString()))} MATIC`, difference: `${utils.formatEther(BigNumber.from(diffInMATIC.toString()))} MATIC`, explorerLink: `${networkExplorers[network]}/address/${address}`, triggerCondition: 'MATIC balance decrease' } lastBalance = newBalance this.emit(emitEventKey, returnNodeExecutionData(returnItem)) } else { lastBalance = newBalance } } } // Start the cron-jobs if (Object.prototype.hasOwnProperty.call(this.cronJobs, emitEventKey)) { for (const cronTime of cronTimes) { // Automatically start the cron job this.cronJobs[emitEventKey].push(new CronJob(cronTime, executeTrigger, undefined, true)) } } else { for (const cronTime of cronTimes) { // Automatically start the cron job this.cronJobs[emitEventKey] = [new CronJob(cronTime, executeTrigger, undefined, true)] } } } async removeTrigger(nodeData: INodeData): Promise { const emitEventKey = nodeData.emitEventKey as string if (Object.prototype.hasOwnProperty.call(this.cronJobs, emitEventKey)) { const cronJobs = this.cronJobs[emitEventKey] for (const cronJob of cronJobs) { cronJob.stop() } this.removeAllListeners(emitEventKey) } } } module.exports = { nodeClass: MATICBalanceTrigger } ================================================ FILE: packages/components/nodes/MATICTransfer/MATICTransfer.ts ================================================ import { IDbCollection, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, IWallet, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import { getNetworkProvider, getNetworkProvidersList, NETWORK, networkExplorers, networkProviderCredentials, NETWORK_PROVIDER, PolygonNetworks } from '../../src' import { ethers } from 'ethers' class MATICTransfer implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'MATIC Transfer' this.name = 'MATICTransfer' this.icon = 'polygon.svg' this.type = 'action' this.category = 'Cryptocurrency' this.version = 1.0 this.description = 'Send/Transfer MATIC to an address' this.incoming = 1 this.outgoing = 1 this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...PolygonNetworks] }, { label: 'Network Provider', name: 'networkProvider', type: 'asyncOptions', loadMethod: 'getNetworkProviders' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] this.inputParameters = [ { label: 'Wallet To Transfer', name: 'wallet', type: 'asyncOptions', description: 'Wallet account to send/transfer MATIC', loadFromDbCollections: ['Wallet'], loadMethod: 'getWallets' }, { label: 'Address To Receive', name: 'address', type: 'string', default: '', description: 'Address to receive MATIC' }, { label: 'Amount', name: 'amount', type: 'number', description: 'Amount of MATIC to transfer' }, { label: 'Gas Limit', name: 'gasLimit', type: 'number', optional: true, placeholder: '100000', description: 'Maximum price you are willing to pay when sending a transaction' }, { label: 'Max Fee per Gas', name: 'maxFeePerGas', type: 'number', optional: true, placeholder: '200', description: 'The maximum price (in wei) per unit of gas for transaction. See more' }, { label: 'Max Priority Fee per Gas', name: 'maxPriorityFeePerGas', type: 'number', optional: true, placeholder: '5', description: 'The priority fee price (in wei) per unit of gas for transaction. See more' } ] as INodeParams[] } loadMethods = { async getWallets(nodeData: INodeData, dbCollection?: IDbCollection): Promise { const returnData: INodeOptionsValue[] = [] try { if (dbCollection === undefined || !dbCollection || !dbCollection.Wallet) { return returnData } const wallets: IWallet[] = dbCollection.Wallet for (let i = 0; i < wallets.length; i += 1) { const wallet = wallets[i] const data = { label: `${wallet.name} (${wallet.network})`, name: JSON.stringify(wallet), description: wallet.address } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } }, async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK return getNetworkProvidersList(network) } } async run(nodeData: INodeData): Promise { const networksData = nodeData.networks const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (networksData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } try { const walletString = inputParametersData.wallet as string const walletDetails: IWallet = JSON.parse(walletString) const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') // Get wallet instance const walletCredential = JSON.parse(walletDetails.walletCredential) const wallet = new ethers.Wallet(walletCredential.privateKey as string, provider) const address = inputParametersData.address as string const amount = inputParametersData.amount as string const gasLimit = inputParametersData.gasLimit as number const maxFeePerGas = inputParametersData.maxFeePerGas as number const maxPriorityFeePerGas = inputParametersData.maxPriorityFeePerGas as number // Send token const nonce = await provider.getTransactionCount(walletDetails.address) const txOption = { nonce } as any if (gasLimit) txOption.gasLimit = gasLimit if (maxFeePerGas) txOption.maxFeePerGas = maxFeePerGas if (maxPriorityFeePerGas) txOption.maxPriorityFeePerGas = maxPriorityFeePerGas const tx = await wallet.sendTransaction({ to: address, value: ethers.utils.parseEther(amount), ...txOption }) const txReceipt = await tx.wait() const returnItem = { transferFrom: wallet.address, transferTo: address, amount, transactionHash: tx.hash, transactionReceipt: txReceipt as any, link: `${networkExplorers[network]}/tx/${tx.hash}` } return returnNodeExecutionData(returnItem) } catch (error) { throw handleErrorMessage(error) } } } module.exports = { nodeClass: MATICTransfer } ================================================ FILE: packages/components/nodes/Mailchimp/Mailchimp.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' interface Auth { username: string password: string } class Mailchimp implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions: INodeParams[] credentials?: INodeParams[] networks?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Mailchimp' this.name = 'mailchimp' this.icon = 'mailchimp.svg' this.type = 'action' this.category = 'Communication' this.version = 2.0 this.description = 'Execute Mailchimp API integration' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Operation', name: 'operation', type: 'options', options: [ { label: 'Get list of campaigns', name: 'listCampaigns', description: 'Returns the list of campaigns' }, { label: 'Get campaign', name: 'getCampaign', description: 'Return single campaign' }, { label: 'Delete campaign', name: 'deleteCampaign', description: 'It will delete campaigns' }, { label: 'Add user to subscribe list', name: 'addUser', description: 'Add or update user to a subscribe list' }, { label: 'Get user', name: 'getUser', description: 'Get information about a specific audience' }, { label: 'Get list of users', name: 'listUsers', description: 'Get information about list of members in a specific audience list' } ], default: 'listCampaigns' } ] as INodeParams[] this.credentials = [ // credentialMethod is mandatory field { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Mailchimp Credentials', name: 'mailChimpCredential' } ], default: 'mailChimpCredential' } ] as INodeParams[] this.inputParameters = [ { label: 'Campaign', name: 'campaignId', type: 'asyncOptions', loadMethod: 'getCampaigns', show: { 'actions.operation': ['deleteCampaign', 'getCampaign'] } }, { label: 'Audience List', name: 'listId', type: 'asyncOptions', loadMethod: 'getLists', show: { 'actions.operation': ['addUser', 'getUser', 'listUsers'] } }, { label: 'Customer Email', name: 'email', type: 'string', show: { 'actions.operation': ['addUser', 'getUser'] } } ] as INodeParams[] } loadMethods = { async getCampaigns(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const credentials = nodeData.credentials const apiKey = credentials!.apiKey as string const dc = ((apiKey && apiKey.split('-')[1]) || '') as string if (!apiKey || !dc) return returnData try { const authObj: Auth = { username: '', password: apiKey } const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://${dc}.api.mailchimp.com/3.0/campaigns`, headers: { 'Content-Type': 'application/json' }, auth: { ...authObj } } const response = await axios(axiosConfig) const campaigns = response.data?.campaigns campaigns.forEach((campaign: any) => { const data = { label: campaign.settings.title || campaign.web_id, name: campaign.id } as INodeOptionsValue returnData.push(data) }) return returnData } catch (e) { return returnData } }, async getLists(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const credentials = nodeData.credentials const apiKey = credentials!.apiKey as string const dc = ((apiKey && apiKey.split('-')[1]) || '') as string if (!apiKey || !dc) return returnData try { const authObj: Auth = { username: '', password: apiKey } const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://${dc}.api.mailchimp.com/3.0/lists`, headers: { 'Content-Type': 'application/json' }, auth: { ...authObj } } const response = await axios(axiosConfig) const lists = response.data?.lists lists.forEach((list: any) => { const data = { label: list.name || list.web_id, name: list.id } as INodeOptionsValue returnData.push(data) }) return returnData } catch (e) { return returnData } } } async run(nodeData: INodeData): Promise { // function to make calls let authObj: Auth async function makeApiCall(method: string, url: string, operation: string, body?: ICommonObject): Promise { const axiosConfig: AxiosRequestConfig = { method: method as Method, url, headers: { 'Content-Type': 'application/json' }, auth: { ...authObj } } if (method === 'post' && body) axiosConfig.data = body let responseData: any[] = [] try { const response = await axios(axiosConfig) if (response?.data) { responseData.push(response?.data) } } catch (err) { if (operation === 'addUser' && err.response.data.title.includes('Member Exists')) { // dont throw error } else throw handleErrorMessage(err) } return responseData } // function to start running the node const actionData = nodeData.actions const credentials = nodeData.credentials if (actionData === undefined || credentials === undefined) { throw handleErrorMessage({ message: 'Required data missing' }) } const operation = actionData.operation as string const apiKey = credentials.apiKey as string const dc = ((apiKey && apiKey.split('-')[1]) || '') as string if (!apiKey) { throw handleErrorMessage({ message: 'Api key is required' }) } if (!dc) { throw handleErrorMessage({ message: 'Date center is required' }) } let campaignId if (['deleteCampaign', 'getCampaign'].includes(operation)) { if (nodeData?.inputParameters?.campaignId === undefined) throw handleErrorMessage({ message: 'Campaign id is required' }) else { campaignId = nodeData?.inputParameters?.campaignId } } let returnData: ICommonObject[] = [] let url = `https://${dc}.api.mailchimp.com/3.0/campaigns` authObj = { username: '', password: apiKey } if (['deleteCampaign', 'getCampaign'].includes(operation)) url += `/${campaignId}` if (operation === 'listCampaigns') { returnData = await makeApiCall('get', url, operation) } else if (operation === 'getCampaign') { returnData = await makeApiCall('get', url, operation) } else if (operation === 'deleteCampaign') { returnData = await makeApiCall('delete', url, operation) } else if (operation === 'getUser') { const audienceList = nodeData?.inputParameters?.listId as string const email = nodeData?.inputParameters?.email as string url = `https://${dc}.api.mailchimp.com/3.0/lists/${audienceList}/members/${email}` returnData = await makeApiCall('get', url, operation) } else if (operation === 'listUsers') { const audienceList = nodeData?.inputParameters?.listId as string url = `https://${dc}.api.mailchimp.com/3.0/lists/${audienceList}/members` returnData = await makeApiCall('get', url, operation) } else if (operation === 'addUser') { const audienceList = nodeData?.inputParameters?.listId as string const email = nodeData?.inputParameters?.email as string url = `https://${dc}.api.mailchimp.com/3.0/lists/${audienceList}/members` const body = { email_address: email, status: 'subscribed' } await makeApiCall('post', url, operation, body) url = `https://${dc}.api.mailchimp.com/3.0/lists/${audienceList}/members/${email}` returnData = await makeApiCall('get', url, operation) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: Mailchimp } ================================================ FILE: packages/components/nodes/Moralis/Moralis.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' import axios, { AxiosRequestConfig, AxiosRequestHeaders, Method } from 'axios' import { MoralisSupportedNetworks } from './supportedNetwork' import { getBlock, getContractEvents, getDateToBlock, getNativeBalance, getTokenBalances, getTransaction, nativeEvmOperation, runContractFunction } from './extendedEVMOperation' import { getContractNFTs, getNFTLowestPrice, getWalletNFTs, getNFTsForContract, getNFTTrades, getWalletNFTTransfers, getNFTTransfersByBlock, getNFTTokenIdMetadata, getWalletNFTCollections, getNFTTokenIdTransfers, nftOperation, reSyncMetadata } from './extendedNFTOperation' import { defiOperation, getPairAddress, getPairReserves } from './extendedDeFiOperation' class Moralis implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Moralis' this.name = 'moralis' this.icon = 'moralis.svg' this.type = 'action' this.category = 'Network Provider' this.version = 1.0 this.description = 'Execute Moralis APIs' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'API', name: 'api', type: 'options', options: [ { label: 'EVM API', name: 'evmAPI', description: 'API for interacting/fetching standard onchain data using Moralis API key.' }, { label: 'NFT API', name: 'nftAPI', description: 'API for interacting/fetching NFT data using Moralis API key.' }, { label: 'DeFi API', name: 'defiAPI', description: 'API for interacting/fetching DeFi data using Moralis API key.' }, { label: 'Upload to IPFS', name: 'uploadFolder', description: 'Upload multiple files in a folder to IPFS and place them in a folder directory.' } ], default: 'evmAPI' }, { label: 'Folder', name: 'folderContent', type: 'folder', description: 'The path to a folder to be uploaded.', show: { 'actions.api': ['uploadFolder'] } } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Moralis API Key', name: 'moralisApi' } ], default: 'moralisApi' } ] as INodeParams[] this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...MoralisSupportedNetworks], hide: { 'actions.api': ['uploadFolder'] } } ] as INodeParams[] this.inputParameters = [ { label: 'Operation', name: 'operation', type: 'asyncOptions', loadMethod: 'getOperations', hide: { 'actions.api': ['uploadFolder'] } }, /** * nativeEvmOperation */ ...getBlock, ...getDateToBlock, ...getTransaction, ...getContractEvents, ...runContractFunction, ...getNativeBalance, ...getTokenBalances, /** * nftOperation */ ...getNFTTransfersByBlock, ...getWalletNFTs, ...getWalletNFTTransfers, ...getWalletNFTCollections, ...getNFTsForContract, ...getNFTTrades, ...getNFTLowestPrice, ...getContractNFTs, ...reSyncMetadata, ...getNFTTokenIdMetadata, ...getNFTTokenIdTransfers, /** * defiOperation */ ...getPairReserves, ...getPairAddress ] } loadMethods = { async getOperations(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const actionData = nodeData.actions if (actionData === undefined) { return returnData } const api = actionData.api as string if (api === 'evmAPI') { return nativeEvmOperation } else if (api === 'nftAPI') { return nftOperation } else if (api === 'defiAPI') { return defiOperation } else if (api === 'ipfsAPI') { return [ { label: 'Upload Folder', name: 'uploadFolder', description: 'Upload multiple files in a folder to IPFS and place them in a folder directory.' } ] } else { return returnData } } } async run(nodeData: INodeData): Promise { const actionData = nodeData.actions const networksData = nodeData.networks const inputParametersData = nodeData.inputParameters const credentials = nodeData.credentials if (actionData === undefined || networksData === undefined || credentials === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } const api = actionData.api as string const operation = inputParametersData.operation as string const chain = networksData.network as string const apiKey = credentials.apiKey as string const returnData: ICommonObject[] = [] let responseData: any let url = '' const queryParameters: ICommonObject = {} if (chain) queryParameters['chain'] = chain let queryBody: any = {} let method: Method = 'GET' const headers: AxiosRequestHeaders = { 'Content-Type': 'application/json', 'X-Api-Key': apiKey } try { /** * nativeEvmOperation */ if (operation === 'getBlock') { const block_number_or_hash = inputParametersData.block_number_or_hash as string url = `https://deep-index.moralis.io/api/v2/block/${block_number_or_hash}` const subdomain = inputParametersData.subdomain as string if (subdomain) queryParameters['subdomain'] = chain } else if (operation === 'getDateToBlock') { url = 'https://deep-index.moralis.io/api/v2/dateToBlock' const providerUrl = inputParametersData.providerUrl as string const date = Date.parse(inputParametersData.date as string) if (providerUrl) queryParameters['providerUrl'] = providerUrl if (date) queryParameters['date'] = date } else if (operation === 'getTransaction') { const transaction_hash = inputParametersData.transaction_hash as string url = `https://deep-index.moralis.io/api/v2/transaction/${transaction_hash}` const subdomain = inputParametersData.subdomain as string if (subdomain) queryParameters['subdomain'] = chain } else if (operation === 'getContractEvents') { method = 'POST' const address = inputParametersData.address as string url = `https://deep-index.moralis.io/api/v2/${address}/events` const topic = inputParametersData.topic as string const subdomain = inputParametersData.subdomain as string const providerUrl = inputParametersData.providerUrl as string const from_date = inputParametersData.from_date as string const to_date = inputParametersData.to_date as string const from_block = inputParametersData.from_block as number const to_block = inputParametersData.to_block as number if (topic) queryParameters['topic'] = topic if (subdomain) queryParameters['subdomain'] = chain if (providerUrl) queryParameters['providerUrl'] = providerUrl if (from_date) queryParameters['from_date'] = Date.parse(from_date) if (to_date) queryParameters['to_date'] = Date.parse(to_date) if (from_block) queryParameters['from_block'] = from_block if (to_block) queryParameters['to_block'] = to_block } else if (operation === 'runContractFunction') { method = 'POST' const address = inputParametersData.address as string url = `https://deep-index.moralis.io/api/v2/${address}/function` const function_name = inputParametersData.function_name as string const subdomain = inputParametersData.subdomain as string const providerUrl = inputParametersData.providerUrl as string const abi_str = inputParametersData.abi as string let abi = [] if (abi_str) abi = JSON.parse(abi_str.replace(/\s/g, '')) const params_str = inputParametersData.params as string let params = [] if (params_str) params = JSON.parse(params_str.replace(/\s/g, '')) if (function_name) queryParameters['function_name'] = function_name if (subdomain) queryParameters['subdomain'] = chain if (providerUrl) queryParameters['providerUrl'] = providerUrl if (abi_str) queryBody['abi'] = abi if (params_str) queryBody['params'] = params } else if (operation === 'getTransactions') { const address = inputParametersData.address as string url = `https://deep-index.moralis.io/api/v2/${address}` const subdomain = inputParametersData.subdomain as string const from_date = inputParametersData.from_date as string const to_date = inputParametersData.to_date as string const from_block = inputParametersData.from_block as number const to_block = inputParametersData.to_block as number if (subdomain) queryParameters['subdomain'] = chain if (from_date) queryParameters['from_date'] = Date.parse(from_date) if (to_date) queryParameters['to_date'] = Date.parse(to_date) if (from_block) queryParameters['from_block'] = from_block if (to_block) queryParameters['to_block'] = to_block } else if (operation === 'getNativeBalance') { const address = inputParametersData.address as string url = `https://deep-index.moralis.io/api/v2/${address}/balance` const providerUrl = inputParametersData.providerUrl as string const to_block = inputParametersData.to_block as number if (providerUrl) queryParameters['providerUrl'] = providerUrl if (to_block) queryParameters['to_block'] = to_block } else if (operation === 'getTokenBalances') { const address = inputParametersData.address as string url = `https://deep-index.moralis.io/api/v2/${address}/erc20` const subdomain = inputParametersData.subdomain as string const to_block = inputParametersData.to_block as number const token_addresses_str = inputParametersData.token_addresses as string let token_addresses = [] if (token_addresses_str) token_addresses = JSON.parse(token_addresses_str.replace(/\s/g, '')) if (subdomain) queryParameters['subdomain'] = subdomain if (to_block) queryParameters['to_block'] = to_block if (token_addresses) queryParameters['token_addresses'] = token_addresses } else if (operation === 'getTokenTransfers') { const address = inputParametersData.address as string url = `https://deep-index.moralis.io/api/v2/${address}/erc20/transfers` const subdomain = inputParametersData.subdomain as string const from_date = inputParametersData.from_date as string const to_date = inputParametersData.to_date as string const from_block = inputParametersData.from_block as number const to_block = inputParametersData.to_block as number if (subdomain) queryParameters['subdomain'] = chain if (from_date) queryParameters['from_date'] = Date.parse(from_date) if (to_date) queryParameters['to_date'] = Date.parse(to_date) if (from_block) queryParameters['from_block'] = from_block if (to_block) queryParameters['to_block'] = to_block } /** * nftOperation */ if (operation === 'getNFTTransfersByBlock') { const block_number_or_hash = inputParametersData.block_number_or_hash as string url = `https://deep-index.moralis.io/api/v2/block/${block_number_or_hash}/nft/transfers` const subdomain = inputParametersData.subdomain as string if (subdomain) queryParameters['subdomain'] = chain } else if (operation === 'getWalletNFTs') { const address = inputParametersData.address as string url = `https://deep-index.moralis.io/api/v2/${address}/nft` const format = inputParametersData.format as string if (format) queryParameters['format'] = format const token_addresses_str = inputParametersData.token_addresses as string let token_addresses = [] if (token_addresses_str) { token_addresses = JSON.parse(token_addresses_str.replace(/\s/g, '')) queryParameters['token_addresses'] = token_addresses } } else if (operation === 'getWalletNFTTransfers') { const address = inputParametersData.address as string url = `https://deep-index.moralis.io/api/v2/${address}/nft/transfers` const format = inputParametersData.format as string const direction = inputParametersData.direction as string const from_block = inputParametersData.from_block as number const to_block = inputParametersData.to_block as number if (format) queryParameters['format'] = format if (direction) queryParameters['direction'] = direction if (from_block) queryParameters['from_block'] = from_block if (to_block) queryParameters['to_block'] = to_block } else if (operation === 'getWalletNFTCollections') { const address = inputParametersData.address as string url = `https://deep-index.moralis.io/api/v2/${address}/nft/collections` } else if (operation === 'getNFTsForContract') { const address = inputParametersData.address as string const token_address = inputParametersData.token_address as string url = `https://deep-index.moralis.io/api/v2/${address}/nft/${token_address}` const format = inputParametersData.format as string if (format) queryParameters['format'] = format } else if (operation === 'getNFTTrades') { const address = inputParametersData.address as string url = `https://deep-index.moralis.io/api/v2/nft/${address}/trades` const marketplace = inputParametersData.marketplace as string const format = inputParametersData.format as string const providerUrl = inputParametersData.providerUrl as string const from_block = inputParametersData.from_block as number const to_block = inputParametersData.to_block as number const from_date = inputParametersData.from_date as string const to_date = inputParametersData.to_date as string if (format) queryParameters['format'] = format if (marketplace) queryParameters['marketplace'] = marketplace if (providerUrl) queryParameters['providerUrl'] = providerUrl if (from_block) queryParameters['from_block'] = from_block if (to_block) queryParameters['to_block'] = to_block if (from_date) queryParameters['from_date'] = Date.parse(from_date) if (to_date) queryParameters['to_date'] = Date.parse(to_date) } else if (operation === 'getNFTLowestPrice') { const address = inputParametersData.address as string url = `https://deep-index.moralis.io/api/v2/nft/${address}/lowestprice` const marketplace = inputParametersData.marketplace as string const days = inputParametersData.format as number const providerUrl = inputParametersData.providerUrl as string if (days) queryParameters['days'] = days if (marketplace) queryParameters['marketplace'] = marketplace if (providerUrl) queryParameters['providerUrl'] = providerUrl } else if (operation === 'getNFTTransfersFromToBlock') { url = `https://deep-index.moralis.io/api/v2/nft/transfers` const format = inputParametersData.format as string const from_block = inputParametersData.from_block as number const to_block = inputParametersData.to_block as number const from_date = inputParametersData.from_date as string const to_date = inputParametersData.to_date as string if (format) queryParameters['format'] = format if (from_block) queryParameters['from_block'] = from_block if (to_block) queryParameters['to_block'] = to_block if (from_date) queryParameters['from_date'] = Date.parse(from_date) if (to_date) queryParameters['to_date'] = Date.parse(to_date) } else if (operation === 'getContractNFTs') { const address = inputParametersData.address as string url = `https://deep-index.moralis.io/api/v2/nft/${address}` const format = inputParametersData.format as string const totalRanges = inputParametersData.totalRanges as number const range = inputParametersData.range as number if (format) queryParameters['format'] = format if (totalRanges) queryParameters['totalRanges'] = totalRanges if (range) queryParameters['range'] = range } else if (operation === 'getNFTContractTransfers') { const address = inputParametersData.address as string url = `https://deep-index.moralis.io/api/v2/nft/${address}/transfers` const format = inputParametersData.format as string if (format) queryParameters['format'] = format } else if (operation === 'getNFTOwners') { const address = inputParametersData.address as string url = `https://deep-index.moralis.io/api/v2/nft/${address}/owners` const format = inputParametersData.format as string if (format) queryParameters['format'] = format } else if (operation === 'getNFTMetadata') { const address = inputParametersData.address as string url = `https://deep-index.moralis.io/api/v2/nft/${address}/metadata` } else if (operation === 'reSyncMetadata') { const address = inputParametersData.address as string const token_id = inputParametersData.token_id as string url = `https://deep-index.moralis.io/api/v2/nft/${address}/${token_id}/metadata/resync` const flag = inputParametersData.flag as string const mode = inputParametersData.mode as string if (flag) queryParameters['flag'] = flag if (mode) queryParameters['mode'] = mode } else if (operation === 'syncNFTContract') { method = 'PUT' const address = inputParametersData.address as string url = `https://deep-index.moralis.io/api/v2/nft/${address}/sync` } else if (operation === 'getNFTTokenIdMetadata') { const address = inputParametersData.address as string const token_id = inputParametersData.token_id as string url = `https://deep-index.moralis.io/api/v2/nft/${address}/${token_id}` const format = inputParametersData.format as string if (format) queryParameters['format'] = format } else if (operation === 'getNFTTokenIdOwners') { const address = inputParametersData.address as string const token_id = inputParametersData.token_id as string url = `https://deep-index.moralis.io/api/v2/nft/${address}/${token_id}/owners` const format = inputParametersData.format as string if (format) queryParameters['format'] = format } else if (operation === 'getNFTTokenIdTransfers') { const address = inputParametersData.address as string const token_id = inputParametersData.token_id as string url = `https://deep-index.moralis.io/api/v2/nft/${address}/${token_id}/transfers` const format = inputParametersData.format as string const order = inputParametersData.order as string if (format) queryParameters['format'] = format if (order) queryParameters['order'] = order } /** * defiOperation */ if (operation === 'getPairReserves') { const pair_address = inputParametersData.pair_address as string url = `https://deep-index.moralis.io/api/v2/${pair_address}/reserves` const to_block = inputParametersData.to_block as number const to_date = inputParametersData.to_date as string if (to_block) queryParameters['to_block'] = to_block if (to_date) queryParameters['to_date'] = Date.parse(to_date) } else if (operation === 'getPairAddress') { const token0_address = inputParametersData.token0_address as string const token1_address = inputParametersData.token1_address as string url = `https://deep-index.moralis.io/api/v2/${token0_address}/${token1_address}/pairAddress` const to_block = inputParametersData.to_block as number const to_date = inputParametersData.to_date as string const exchange = inputParametersData.exchange as string if (to_block) queryParameters['to_block'] = to_block if (to_date) queryParameters['to_date'] = Date.parse(to_date) if (exchange) queryParameters['exchange'] = exchange } /** * ipfsOperation */ if (api === 'uploadFolder') { method = 'POST' url = 'https://deep-index.moralis.io/api/v2/ipfs/uploadFolder' const bodyParams = [] const folderContent = actionData.folderContent as string const base64Array = JSON.parse(folderContent.replace(/\s/g, '')) for (let i = 0; i < base64Array.length; i += 1) { const fileBase64 = base64Array[i] const splitDataURI = fileBase64.split(',') const filepath = (splitDataURI.pop() || 'filepath:').split(':')[1] const content = splitDataURI.pop() || '' bodyParams.push({ path: filepath, content }) } queryBody = bodyParams } const axiosConfig: AxiosRequestConfig = { method, url, headers } if (Object.keys(queryParameters).length > 0) { axiosConfig.params = queryParameters axiosConfig.paramsSerializer = (params) => serializeQueryParams(params, true) } if (Object.keys(queryBody).length > 0) { axiosConfig.data = queryBody } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) returnData.push(...responseData) else returnData.push(responseData) return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: Moralis } ================================================ FILE: packages/components/nodes/Moralis/extendedDeFiOperation.ts ================================================ import { INodeOptionsValue, INodeParams } from '../../src' export const defiOperation = [ { label: 'Get Pair Reserves', name: 'getPairReserves', description: 'Get the liquidity reserves for a given pair address. Only Uniswap V2 based exchanges supported at the moment.' }, { label: 'Get Pair Address', name: 'getPairAddress', description: 'Fetch the pair data of the provided token0+token1 combination. The token0 and token1 options are interchangable (ie. there is no different outcome in "token0=WETH and token1=USDT" or "token0=USDT and token1=WETH")' } ] as INodeOptionsValue[] export const getPairReserves = [ { label: 'Pair Address', name: 'pairAddress', type: 'string', description: 'Liquidity pair address', show: { 'inputParameters.operation': ['getPairReserves'] } }, { label: 'To Block', name: 'to_block', type: 'string', description: 'To get the reserves at this block number', show: { 'inputParameters.operation': ['getPairReserves'] } }, { label: 'To Date', name: 'to_date', type: 'date', description: 'Get the reserves to this date', show: { 'inputParameters.operation': ['getPairReserves'] } } ] as INodeParams[] export const getPairAddress = [ { label: 'Token0 Address', name: 'token0_address', type: 'string', description: 'Token0 address', show: { 'inputParameters.operation': ['getPairAddress'] } }, { label: 'Token1 Address', name: 'token1_address', type: 'string', description: 'Token1 address', show: { 'inputParameters.operation': ['getPairAddress'] } }, { label: 'Exchange', name: 'exchange', type: 'options', description: 'The factory name or address of the token exchange', options: [ { label: 'UniSwapV2', name: 'uniswapv2' }, { label: 'UniSwapV3', name: 'uniswapv3' }, { label: 'SushiSwapV2', name: 'sushiswapv2' }, { label: 'PancakeSwapV2', name: 'pancakeswapv2' }, { label: 'PancakeSwapV1', name: 'pancakeswapv1' }, { label: 'QuickSwap', name: 'quickswap' } ], default: 'uniswapv2', show: { 'inputParameters.operation': ['getPairAddress'] } }, { label: 'To Block', name: 'to_block', type: 'string', description: 'To get the reserves at this block number', show: { 'inputParameters.operation': ['getPairAddress'] } }, { label: 'To Date', name: 'to_date', type: 'date', description: 'Get the reserves to this date', show: { 'inputParameters.operation': ['getPairAddress'] } } ] as INodeParams[] ================================================ FILE: packages/components/nodes/Moralis/extendedEVMOperation.ts ================================================ import { INodeOptionsValue, INodeParams } from '../../src' export const nativeEvmOperation = [ { label: 'Get Native Balance', name: 'getNativeBalance', description: 'Get native balance for a specific address.' }, { label: 'Get Token Balances', name: 'getTokenBalances', description: 'Get token balances for a specific address.' }, { label: 'Get Token Transfers', name: 'getTokenTransfers', description: 'Get ERC20 token transactions ordered by block number in descending order.' }, { label: 'Get Date To Block', name: 'getDateToBlock', description: 'Get the closest block of the provided date.' }, { label: 'Get Block', name: 'getBlock', description: 'Get the contents of a block by block hash.' }, { label: 'Get Transaction', name: 'getTransaction', description: 'Get the contents of a transaction by transaction hash.' }, { label: 'Get Transactions', name: 'getTransactions', description: 'Get native transactions ordered by block number in descending order.' }, { label: 'Get Contract Events', name: 'getContractEvents', description: 'Get events for a specific contract ordered by block number in descending order.' }, { label: 'Run Contract Function', name: 'runContractFunction', description: 'Run a given function of a contract abi and retrieve readonly data.' } ] as INodeOptionsValue[] export const getDateToBlock = [ { label: 'Provider Url', name: 'providerUrl', type: 'string', optional: true, description: 'Web3 provider url to user when using local dev chain', show: { 'inputParameters.operation': ['getDateToBlock'] } }, { label: 'Date', name: 'date', type: 'date', optional: true, show: { 'inputParameters.operation': ['getDateToBlock'] } } ] as INodeParams[] export const getBlock = [ { label: 'Block Number or Hash', name: 'block_number_or_hash', type: 'string', description: 'The block hash or block number', show: { 'inputParameters.operation': ['getBlock', 'getNFTTransfersByBlock'] } }, { label: 'Subdomain', name: 'subdomain', type: 'string', optional: true, description: 'The subdomain of the moralis server to use (Only use when selecting local devchain as chain)', show: { 'inputParameters.operation': ['getBlock', 'getNFTTransfersByBlock', 'getTransaction'] } } ] as INodeParams[] export const getTransaction = [ { label: 'Transaction Hash', name: 'transaction_hash', type: 'string', description: 'The transaction hash', show: { 'inputParameters.operation': ['getTransaction'] } } ] as INodeParams[] export const getContractEvents = [ { label: 'Address', name: 'address', type: 'string', description: 'Address of the contract', show: { 'inputParameters.operation': ['getContractEvents', 'getTransactions', 'getTokenTransfers'] } }, { label: 'Topic', name: 'topic', type: 'string', description: 'The topic of the event', show: { 'inputParameters.operation': ['getContractEvents'] } }, { label: 'Subdomain', name: 'subdomain', type: 'string', optional: true, description: 'The subdomain of the moralis server to use (Only use when selecting local devchain as chain)', show: { 'inputParameters.operation': ['getContractEvents', 'getTransactions', 'getTokenTransfers'] } }, { label: 'Provider Url', name: 'providerUrl', type: 'string', optional: true, description: 'Web3 provider url to user when using local dev chain', show: { 'inputParameters.operation': ['getContractEvents'] } }, { label: 'From Date', name: 'from_date', type: 'date', description: 'The date from where to get the logs', optional: true, show: { 'inputParameters.operation': ['getContractEvents', 'getTransactions', 'getTokenTransfers'] } }, { label: 'To Date', name: 'to_date', type: 'date', description: 'Get the logs to this date', optional: true, show: { 'inputParameters.operation': ['getContractEvents', 'getTransactions', 'getTokenTransfers'] } }, { label: 'From Block', name: 'from_block', type: 'number', description: 'The minimum block number from where to get the logs', optional: true, show: { 'inputParameters.operation': ['getContractEvents', 'getTransactions', 'getTokenTransfers'] } }, { label: 'To Block', name: 'to_block', type: 'number', description: 'The maximum block number from where to get the logs.', optional: true, show: { 'inputParameters.operation': ['getContractEvents', 'getTransactions', 'getTokenTransfers'] } } ] as INodeParams[] export const runContractFunction = [ { label: 'Address', name: 'address', type: 'string', description: 'Address of the contract', show: { 'inputParameters.operation': ['runContractFunction'] } }, { label: 'Function Name', name: 'function_name', type: 'string', description: 'Function Name of the contract to run', show: { 'inputParameters.operation': ['runContractFunction'] } }, { label: 'ABI', name: 'abi', type: 'json', placeholder: '[ "event Transfer(address indexed from, address indexed to, uint value)" ]', description: 'ABI of the contract in array', show: { 'inputParameters.operation': ['runContractFunction'] } }, { label: 'Function Parameters', name: 'params', type: 'json', placeholder: '{"var1": "value1"}', description: 'Function parameters in json. Ex: {"var1": "value1"}', optional: true, show: { 'inputParameters.operation': ['runContractFunction'] } }, { label: 'Subdomain', name: 'subdomain', type: 'string', optional: true, description: 'The subdomain of the moralis server to use (Only use when selecting local devchain as chain)', show: { 'inputParameters.operation': ['runContractFunction'] } }, { label: 'Provider Url', name: 'providerUrl', type: 'string', optional: true, description: 'Web3 provider url to user when using local dev chain', show: { 'inputParameters.operation': ['runContractFunction'] } } ] as INodeParams[] export const getNativeBalance = [ { label: 'Address', name: 'address', type: 'string', description: 'The address for which the native balance will be checked', show: { 'inputParameters.operation': ['getNativeBalance'] } }, { label: 'Provider Url', name: 'providerUrl', type: 'string', optional: true, description: 'Web3 provider url to user when using local dev chain', show: { 'inputParameters.operation': ['getNativeBalance'] } }, { label: 'To Block', name: 'to_block', type: 'number', description: 'The block number on which the balances should be checked.', optional: true, show: { 'inputParameters.operation': ['getNativeBalance'] } } ] as INodeParams[] export const getTokenBalances = [ { label: 'Address', name: 'address', type: 'string', description: 'The address for which token balances will be checked', show: { 'inputParameters.operation': ['getTokenBalances'] } }, { label: 'Token Addresses', name: 'token_addresses', type: 'json', placeholder: '["0xa, "0xb"]', optional: true, description: 'The addresses to get balances for', show: { 'inputParameters.operation': ['getTokenBalances'] } }, { label: 'Subdomain', name: 'subdomain', type: 'string', optional: true, description: 'The subdomain of the moralis server to use (Only use when selecting local devchain as chain)', show: { 'inputParameters.operation': ['getTokenBalances'] } }, { label: 'To Block', name: 'to_block', type: 'number', description: 'The block number on which the balances should be checked.', optional: true, show: { 'inputParameters.operation': ['getTokenBalances'] } } ] as INodeParams[] ================================================ FILE: packages/components/nodes/Moralis/extendedNFTOperation.ts ================================================ import { INodeOptionsValue, INodeParams } from '../../src' export const nftOperation = [ { label: 'Get Wallet NFTs', name: 'getWalletNFTs', description: 'Get NFTs owned by a given address.' }, { label: 'Get Wallet NFT Transfers', name: 'getWalletNFTTransfers', description: 'Get the transfers of the tokens matching the given parameters.' }, { label: 'Get Wallet NFT Collections', name: 'getWalletNFTCollections', description: 'Get the nft collections owned by an user' }, { label: 'Get NFTs For Contract', name: 'getNFTsForContract', description: 'Get NFTs owned by the given address for a specific NFT contract address.' }, { label: 'Get NFT Trades', name: 'getNFTTrades', description: 'Get the nft trades for a given contract and marketplace.' }, { label: 'Get NFT Lowest Price', name: 'getNFTLowestPrice', description: 'Get the lowest executed price for an NFT token contract for the last x days (only trades paid in ETH).' }, { label: 'Get NFT Transfers By Block', name: 'getNFTTransfersByBlock', description: 'Get NFT transfers by block number or block hash.' }, { label: 'Get NFT Transfers From To Block', name: 'getNFTTransfersFromToBlock', description: 'Gets the transfers of the tokens from a block number to a block number.' }, { label: 'Get Contract NFTs', name: 'getContractNFTs', description: 'Get all NFTs, including metadata (where available), for all NFTs for the given contract address.' }, { label: 'Get NFT Contract Transfers', name: 'getNFTContractTransfers', description: 'Get the transfers of the tokens matching the given parameters.' }, { label: 'Get NFT Owners', name: 'getNFTOwners', description: 'Get all owners of NFTs within a given contract.' }, { label: 'Get NFT Metadata', name: 'getNFTMetadata', description: 'Get the contract level metadata (name, symbol, base token uri) for the given contract.' }, { label: 'ReSync Metadata', name: 'reSyncMetadata', description: 'ReSync the metadata for an NFT.' }, { label: 'Sync NFT Contract', name: 'syncNFTContract', description: 'Initiates a metadata refresh for an entire NFT collection.' }, { label: 'Get NFT Token Id Metadata', name: 'getNFTTokenIdMetadata', description: 'Get NFT data, including metadata (where available), for the given NFT token id of the given contract address.' }, { label: 'Get NFT Token Id Owners', name: 'getNFTTokenIdOwners', description: 'Get all owners of a specific NFT given the contract address and token ID.' }, { label: 'Get NFT Token Id Transfers', name: 'getNFTTokenIdTransfers', description: 'Get the transfers of an NFT given a contract address and token ID.' } ] as INodeOptionsValue[] export const getWalletNFTs = [ { label: 'Address', name: 'address', type: 'string', description: 'The owner of a given token', show: { 'inputParameters.operation': ['getWalletNFTs'] } }, { label: 'Format', name: 'format', type: 'options', optional: true, description: 'The format of the token id', options: [ { label: 'Decimal', name: 'decimal' }, { label: 'Hex', name: 'hex' } ], default: 'decimal', show: { 'inputParameters.operation': ['getWalletNFTs'] } }, { label: 'Token Addresses', name: 'token_addresses', type: 'json', placeholder: '["0xa, "0xb"]', optional: true, description: 'The addresses to get balances for', show: { 'inputParameters.operation': ['getWalletNFTs'] } } ] as INodeParams[] export const getNFTTransfersByBlock = [ { label: 'Block Number or Hash', name: 'block_number_or_hash', type: 'string', description: 'The block hash or block number', show: { 'inputParameters.operation': ['getNFTTransfersByBlock'] } }, { label: 'Subdomain', name: 'subdomain', type: 'string', optional: true, description: 'The subdomain of the moralis server to use (Only use when selecting local devchain as chain)', show: { 'inputParameters.operation': ['getNFTTransfersByBlock'] } } ] as INodeParams[] export const getWalletNFTTransfers = [ { label: 'Address', name: 'address', type: 'string', description: 'The sender or recepient of the transfer', show: { 'inputParameters.operation': ['getWalletNFTTransfers'] } }, { label: 'Format', name: 'format', type: 'options', optional: true, description: 'The format of the token id', options: [ { label: 'Decimal', name: 'decimal' }, { label: 'Hex', name: 'hex' } ], default: 'decimal', show: { 'inputParameters.operation': ['getWalletNFTTransfers'] } }, { label: 'Direction', name: 'direction', type: 'options', optional: true, description: 'The transfer direction', options: [ { label: 'Both', name: 'both' }, { label: 'From', name: 'from' }, { label: 'To', name: 'to' } ], default: 'both', show: { 'inputParameters.operation': ['getWalletNFTTransfers'] } }, { label: 'From Block', name: 'from_block', type: 'number', description: 'The minimum block number from where to get the transfers', optional: true, show: { 'inputParameters.operation': ['getWalletNFTTransfers'] } }, { label: 'To Block', name: 'to_block', type: 'number', description: 'To get the reserves at this block number', optional: true, show: { 'inputParameters.operation': ['getWalletNFTTransfers'] } } ] as INodeParams[] export const getWalletNFTCollections = [ { label: 'Address', name: 'address', type: 'string', description: 'The owner wallet address of the NFT collections', show: { 'inputParameters.operation': ['getWalletNFTCollections'] } } ] as INodeParams[] export const getNFTsForContract = [ { label: 'Address', name: 'address', type: 'string', description: 'The owner of a given token', show: { 'inputParameters.operation': ['getNFTsForContract'] } }, { label: 'Token Address', name: 'token_address', type: 'string', description: 'Address of the contract', show: { 'inputParameters.operation': ['getNFTsForContract'] } }, { label: 'Format', name: 'format', type: 'options', optional: true, description: 'The format of the token id', options: [ { label: 'Decimal', name: 'decimal' }, { label: 'Hex', name: 'hex' } ], default: 'decimal', show: { 'inputParameters.operation': ['getNFTsForContract', 'getNFTTransfersFromToBlock'] } } ] as INodeParams[] export const getNFTTrades = [ { label: 'Address', name: 'address', type: 'string', description: 'Address of the contract', show: { 'inputParameters.operation': ['getNFTTrades', 'getNFTLowestPrice'] } }, { label: 'Marketplace', name: 'marketplace', type: 'options', optional: true, description: 'Marketplace from where to get the trades', options: [ { label: 'Opensea', name: 'opensea' } ], default: 'opensea', show: { 'inputParameters.operation': ['getNFTTrades', 'getNFTLowestPrice'] } }, { label: 'Provider Url', name: 'providerUrl', type: 'string', optional: true, description: 'Web3 provider url to user when using local dev chain', show: { 'inputParameters.operation': ['getNFTTrades', 'getNFTLowestPrice'] } }, { label: 'From Date', name: 'from_date', type: 'date', description: 'The date from where to get the transfers ', optional: true, show: { 'inputParameters.operation': ['getNFTTrades', 'getNFTTransfersFromToBlock'] } }, { label: 'To Date', name: 'to_date', type: 'date', description: 'Get the transfers to this date', optional: true, show: { 'inputParameters.operation': ['getNFTTrades', 'getNFTTransfersFromToBlock'] } }, { label: 'From Block', name: 'from_block', type: 'number', description: 'The minimum block number from where to get the transfers.', optional: true, show: { 'inputParameters.operation': ['getNFTTrades', 'getNFTTransfersFromToBlock'] } }, { label: 'To Block', name: 'to_block', type: 'number', description: 'The maximum block number from where to get the transfers.', optional: true, show: { 'inputParameters.operation': ['getNFTTrades', 'getNFTTransfersFromToBlock'] } } ] as INodeParams[] export const getNFTLowestPrice = [ { label: 'Days', name: 'days', type: 'number', description: 'The number of days to look back to find the lowest price. If not provided 7 days will be the default', optional: true, show: { 'inputParameters.operation': ['getNFTLowestPrice'] } } ] as INodeParams[] export const getContractNFTs = [ { label: 'Address', name: 'address', type: 'string', description: 'Address of the contract', show: { 'inputParameters.operation': ['getContractNFTs', 'getNFTContractTransfers', 'getNFTOwners', 'getNFTMetadata'] } }, { label: 'Total Ranges', name: 'totalRanges', type: 'number', optional: true, description: 'The number of subranges to split the results into', show: { 'inputParameters.operation': ['getContractNFTs'] } }, { label: 'Range', name: 'range', type: 'number', optional: true, description: 'The desired subrange to query', show: { 'inputParameters.operation': ['getContractNFTs'] } }, { label: 'Format', name: 'format', type: 'options', optional: true, description: 'The format of the token id', options: [ { label: 'Decimal', name: 'decimal' }, { label: 'Hex', name: 'hex' } ], default: 'decimal', show: { 'inputParameters.operation': ['getContractNFTs', 'getNFTContractTransfers', 'getNFTOwners'] } } ] as INodeParams[] export const reSyncMetadata = [ { label: 'Address', name: 'address', type: 'string', description: 'Address of the contract', show: { 'inputParameters.operation': ['reSyncMetadata', 'getNFTTokenIdMetadata', 'getNFTTokenIdOwners', 'getNFTTokenIdTransfers'] } }, { label: 'Token Id', name: 'token_id', type: 'string', description: 'The id of the token', show: { 'inputParameters.operation': ['reSyncMetadata', 'getNFTTokenIdMetadata', 'getNFTTokenIdOwners', 'getNFTTokenIdTransfers'] } }, { label: 'Flag', name: 'flag', type: 'options', optional: true, description: 'The type of resync to operate', options: [ { label: 'URI', name: 'uri' }, { label: 'Metadata', name: 'metadata' } ], default: 'uri', show: { 'inputParameters.operation': ['reSyncMetadata'] } }, { label: 'Mode', name: 'mode', type: 'options', optional: true, description: 'To define the behaviour of the endpoint', options: [ { label: 'Async', name: 'async' }, { label: 'Sync', name: 'sync' } ], default: 'async', show: { 'inputParameters.operation': ['reSyncMetadata'] } } ] as INodeParams[] export const getNFTTokenIdMetadata = [ { label: 'Format', name: 'format', type: 'options', optional: true, description: 'The format of the token id', options: [ { label: 'Decimal', name: 'decimal' }, { label: 'Hex', name: 'hex' } ], default: 'decimal', show: { 'inputParameters.operation': ['getNFTTokenIdMetadata', 'getNFTTokenIdOwners', 'getNFTTokenIdTransfers'] } } ] as INodeParams[] export const getNFTTokenIdTransfers = [ { label: 'Order', name: 'order', type: 'string', optional: true, description: 'The field(s) to order on and if it should be ordered in ascending or descending order. Specified by: fieldName1.order,fieldName2.order. Example 1: "block_number", "block_number.ASC", "block_number.DESC", Example 2: "block_number and contract_type", "block_number.ASC,contract_type.DESC"', show: { 'inputParameters.operation': ['getNFTTokenIdTransfers'] } } ] as INodeParams[] ================================================ FILE: packages/components/nodes/Moralis/supportedNetwork.ts ================================================ import { INodeOptionsValue, NETWORK_LABEL } from '../../src' export const MoralisSupportedNetworks = [ { label: NETWORK_LABEL.MAINNET, name: 'eth' }, { label: NETWORK_LABEL.GÖRLI, name: 'goerli' }, { label: NETWORK_LABEL.MATIC, name: 'polygon' }, { label: NETWORK_LABEL.MATIC_MUMBAI, name: 'mumbai' }, { label: NETWORK_LABEL.BSC, name: 'bsc' }, { label: NETWORK_LABEL.BSC_TESTNET, name: 'bsc testnet' }, { label: NETWORK_LABEL.AVALANCHE, name: 'avalanche' }, { label: NETWORK_LABEL.AVALANCHE_TESTNET, name: 'avalanche testnet' }, { label: NETWORK_LABEL.FANTOM, name: 'fantom' }, { label: NETWORK_LABEL.CRONOS, name: 'cronos' }, { label: NETWORK_LABEL.CRONOS_TESTNET, name: 'cronos testnet' } ] as INodeOptionsValue[] ================================================ FILE: packages/components/nodes/NFTMintTrigger/NFTMintTrigger.ts ================================================ import { ethers, utils } from 'ethers' import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams, IProviders, NodeType } from '../../src/Interface' import { returnNodeExecutionData } from '../../src/utils' import EventEmitter from 'events' import { ArbitrumNetworks, ETHNetworks, OptimismNetworks, PolygonNetworks, networkExplorers, openseaExplorers, NETWORK, getNetworkProvidersList, getNetworkProvider, NETWORK_PROVIDER, eventTransferAbi, networkProviderCredentials } from '../../src/ChainNetwork' class NFTMintTrigger extends EventEmitter implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] providers: IProviders constructor() { super() this.label = 'NFT Mint Trigger' this.name = 'NFTMintTrigger' this.icon = 'nftmint.png' this.type = 'trigger' this.category = 'NFT' this.version = 1.0 this.description = 'Start workflow whenever a NFT is minted' this.incoming = 0 this.outgoing = 1 this.providers = {} this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...ETHNetworks, ...PolygonNetworks, ...ArbitrumNetworks, ...OptimismNetworks], default: 'homestead' }, { label: 'Network Provider', name: 'networkProvider', type: 'asyncOptions', loadMethod: 'getNetworkProviders' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] this.inputParameters = [ { label: 'Token Standard', name: 'tokenStandard', type: 'options', options: [ { label: 'ERC-721', name: 'ERC721' } ], default: 'ERC721' }, { label: 'To Wallet Address', name: 'toAddress', type: 'string', default: '' }, { label: 'NFT Address', name: 'nftAddress', type: 'string', default: '', optional: true } ] as INodeParams[] } loadMethods = { async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK return getNetworkProvidersList(network) } } async runTrigger(nodeData: INodeData): Promise { const networksData = nodeData.networks const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (networksData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') const emitEventKey = nodeData.emitEventKey as string const nftAddress = inputParametersData.nftAddress as string const toAddress = inputParametersData.toAddress as string const ifaceABI = eventTransferAbi const topicId = utils.id('Transfer(address,address,uint256)') const filter = { topics: [ topicId, utils.hexZeroPad('0x0000000000000000000000000000000000000000', 32), // Mint always has 000 from address toAddress ? utils.hexZeroPad(toAddress, 32) : null ] } if (nftAddress) (filter as any)['address'] = nftAddress provider.on(filter, async (log: any) => { const txHash = log.transactionHash const iface = new ethers.utils.Interface(ifaceABI) const logs = await provider.getLogs(filter) const events = logs.map((log) => iface.parseLog(log)) const toWallet = events.length ? events[0].args[1] : '' //ERC721 has 4 topics length if (log.topics.length === 4) { const returnItem = {} as ICommonObject returnItem['To Wallet'] = toWallet returnItem['NFT Token Address'] = log.address if (openseaExplorers[network]) { let tokenString = '' const counter = log.topics[log.topics.length - 1] const strippedZeroCounter = utils.hexStripZeros(counter) if (strippedZeroCounter !== '0x') { const counterBigNubmer = ethers.BigNumber.from(strippedZeroCounter) tokenString = counterBigNubmer.toString() } else { tokenString = '0' } returnItem['NFT Token Id'] = tokenString returnItem['txHash'] = txHash returnItem['explorerLink'] = `${networkExplorers[network]}/tx/${txHash}` returnItem['openseaLink'] = `${openseaExplorers[network]}/assets/${log.address}/${tokenString}` } this.emit(emitEventKey, returnNodeExecutionData(returnItem)) } }) this.providers[emitEventKey] = { provider, filter } } async removeTrigger(nodeData: INodeData): Promise { const emitEventKey = nodeData.emitEventKey as string if (Object.prototype.hasOwnProperty.call(this.providers, emitEventKey)) { const provider = this.providers[emitEventKey].provider const filter = this.providers[emitEventKey].filter provider.off(filter) this.removeAllListeners(emitEventKey) } } } module.exports = { nodeClass: NFTMintTrigger } ================================================ FILE: packages/components/nodes/NFTTransferTrigger/NFTTransferTrigger.ts ================================================ import { ethers, utils } from 'ethers' import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams, IProviders, NodeType } from '../../src/Interface' import { returnNodeExecutionData } from '../../src/utils' import EventEmitter from 'events' import { ArbitrumNetworks, ETHNetworks, OptimismNetworks, PolygonNetworks, networkExplorers, openseaExplorers, getNetworkProvidersList, NETWORK, getNetworkProvider, NETWORK_PROVIDER, eventTransferAbi, erc1155SingleTransferAbi, erc1155BatchTransferAbi, networkProviderCredentials } from '../../src/ChainNetwork' class NFTTransferTrigger extends EventEmitter implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] providers: IProviders constructor() { super() this.label = 'NFT Transfer Trigger' this.name = 'NFTTransferTrigger' this.icon = 'nfttransfer.png' this.type = 'trigger' this.category = 'NFT' this.version = 1.0 this.description = 'Start workflow whenever a NFT transfer event happened' this.incoming = 0 this.outgoing = 1 this.providers = {} this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...ETHNetworks, ...PolygonNetworks, ...ArbitrumNetworks, ...OptimismNetworks], default: 'homestead' }, { label: 'Network Provider', name: 'networkProvider', type: 'asyncOptions', loadMethod: 'getNetworkProviders' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] this.inputParameters = [ { label: 'Token Standard', name: 'tokenStandard', type: 'options', options: [ { label: 'ERC-721', name: 'ERC721' }, { label: 'ERC-1155', name: 'ERC1155' } ], default: 'ERC721' }, { label: 'Transfer Method', name: 'tokenMethod', type: 'options', options: [ { label: 'Single', name: 'single' }, { label: 'Batch', name: 'batch' } ], default: 'single', show: { 'inputParameters.tokenStandard': ['ERC1155'] } }, { label: 'NFT Address', name: 'nftAddress', type: 'string', default: '', optional: true }, { label: 'Direction', name: 'direction', type: 'options', options: [ { label: 'From', name: 'from', description: 'Transfer from wallet address' }, { label: 'To', name: 'to', description: 'Transfer to wallet address' }, { label: 'Both From and To', name: 'fromTo', description: 'Transfer from a wallet address to another wallet address' } ], default: '' }, { label: 'From Wallet Address', name: 'fromAddress', type: 'string', default: '', show: { 'inputParameters.direction': ['from', 'fromTo'] } }, { label: 'To Wallet Address', name: 'toAddress', type: 'string', default: '', show: { 'inputParameters.direction': ['to', 'fromTo'] } } ] as INodeParams[] } loadMethods = { async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK return getNetworkProvidersList(network) } } async runTrigger(nodeData: INodeData): Promise { const networksData = nodeData.networks const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (networksData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') const emitEventKey = nodeData.emitEventKey as string const nftAddress = inputParametersData.nftAddress as string const fromAddress = inputParametersData.fromAddress as string const toAddress = inputParametersData.toAddress as string const tokenStandard = inputParametersData.tokenStandard as string const tokenMethod = inputParametersData.tokenMethod as string let ifaceABI = eventTransferAbi let topicId = utils.id('Transfer(address,address,uint256)') if (tokenStandard === 'ERC1155' && tokenMethod === 'single') { topicId = utils.id('TransferSingle(address,address,address,uint256,uint256)') ifaceABI = erc1155SingleTransferAbi } if (tokenStandard === 'ERC1155' && tokenMethod === 'batch') { topicId = utils.id('TransferBatch(address,address,address,uint256[],uint256[])') ifaceABI = erc1155BatchTransferAbi } const filter = { topics: [topicId, fromAddress ? utils.hexZeroPad(fromAddress, 32) : null, toAddress ? utils.hexZeroPad(toAddress, 32) : null] } if (nftAddress) (filter as any)['address'] = nftAddress provider.on(filter, async (log: any) => { const txHash = log.transactionHash const iface = new ethers.utils.Interface(ifaceABI) const logs = await provider.getLogs(filter) const events = logs.map((log) => iface.parseLog(log)) const fromWallet = events.length ? events[0].args[tokenStandard === 'ERC1155' ? 1 : 0] : '' const toWallet = events.length ? events[0].args[tokenStandard === 'ERC1155' ? 2 : 1] : '' //ERC721 or ERC1155 has 4 topics length if (log.topics.length === 4) { const returnItem = {} as ICommonObject returnItem['From Wallet'] = fromWallet returnItem['To Wallet'] = toWallet returnItem['NFT Token Address'] = log.address if (openseaExplorers[network]) { let tokenString = '' const counter = log.topics[log.topics.length - 1] const strippedZeroCounter = utils.hexStripZeros(counter) if (strippedZeroCounter !== '0x') { const counterBigNubmer = ethers.BigNumber.from(strippedZeroCounter) tokenString = counterBigNubmer.toString() } else { tokenString = '0' } returnItem['NFT Token Id'] = tokenString returnItem['txHash'] = txHash returnItem['explorerLink'] = `${networkExplorers[network]}/tx/${txHash}` returnItem['openseaLink'] = `${openseaExplorers[network]}/assets/${log.address}/${tokenString}` } this.emit(emitEventKey, returnNodeExecutionData(returnItem)) } }) this.providers[emitEventKey] = { provider, filter } } async removeTrigger(nodeData: INodeData): Promise { const emitEventKey = nodeData.emitEventKey as string if (Object.prototype.hasOwnProperty.call(this.providers, emitEventKey)) { const provider = this.providers[emitEventKey].provider const filter = this.providers[emitEventKey].filter provider.off(filter) this.removeAllListeners(emitEventKey) } } } module.exports = { nodeClass: NFTTransferTrigger } ================================================ FILE: packages/components/nodes/NodeJS/NodeJS.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { returnNodeExecutionData } from '../../src/utils' import { NodeVM } from 'vm2' class NodeJS implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number inputParameters?: INodeParams[] constructor() { this.label = 'NodeJS' this.name = 'nodeJS' this.icon = 'nodejs.png' this.type = 'action' this.category = 'Development' this.version = 1.0 this.description = 'Execute code within NodeVM sandbox' this.incoming = 1 this.outgoing = 1 this.inputParameters = [ { label: 'Code', name: 'code', type: 'code', default: `console.info($nodeData);\nconst example = 'Hello World!';\nreturn example;`, description: 'Custom code to run' }, { label: 'External Modules', name: 'external', type: 'json', placeholder: '["axios"]', description: 'Import installed dependencies within Outerbridge', optional: true } ] as INodeParams[] } async run(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters if (inputParametersData === undefined) { throw new Error('Required data missing') } const returnData: ICommonObject[] = [] // Global object const sandbox = { $nodeData: nodeData } const options = { console: 'inherit', sandbox, require: { external: false as boolean | { modules: string[] }, builtin: ['*'] } } as any const code = (inputParametersData.code as string) || '' const external = inputParametersData.external as string if (external) { const deps = JSON.parse(external) if (deps && deps.length) { options.require.external = { modules: deps } } } const vm = new NodeVM(options) let responseData: any // tslint:disable-line: no-any try { if (!code) responseData = [] else { responseData = await vm.run(`module.exports = async function() {${code}}()`, __dirname) } } catch (e) { return Promise.reject(e) } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: NodeJS } ================================================ FILE: packages/components/nodes/Notion/Notion.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' class Notion implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Notion' this.name = 'notion' this.icon = 'notion.svg' this.type = 'action' this.category = 'Productivity' this.version = 1.0 this.description = 'Work with Notion page, database or block' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Target', name: 'target', type: 'options', options: [ { label: 'Search all pages', name: 'searchTarget' }, { label: 'Page', name: 'pageTarget' }, { label: 'Database', name: 'databaseTarget' }, { label: 'Block', name: 'blockTarget' } ] }, { label: 'Page Operations', name: 'pageOperation', type: 'options', options: [ { label: 'Get Page', name: 'getPage' }, { label: 'Create Page', name: 'createPage' }, { label: 'Update Page', name: 'updatePage' }, { label: 'Delete Page', name: 'deletePage' } ], default: 'Get Page', show: { 'actions.target': ['pageTarget'] } }, { label: 'Database Operations', name: 'databaseOperation', type: 'options', options: [ { label: 'Get Database', name: 'getDatabase' }, { label: 'Create Database', name: 'createDatabase' }, { label: 'Update Database', name: 'updateDatabase' }, { label: 'Query Database', name: 'queryDatabase' } ], default: 'Get Database', show: { 'actions.target': ['databaseTarget'] } }, { label: 'Block Operations', name: 'blockOperation', type: 'options', options: [ { label: 'Get Block', name: 'getBlock' }, { label: 'Update Block', name: 'updateBlock' }, { label: 'Get Block Children', name: 'getBlockChildren' }, { label: 'Append Block Children', name: 'appendBlockChildren' }, { label: 'Delete Block', name: 'deleteBlock' } ], default: 'Get Block', show: { 'actions.target': ['blockTarget'] } } ] as INodeParams[] this.inputParameters = [ { label: 'Notion Version', name: 'notionVersion', type: 'string', optional: false, description: 'Notion Version to be used in the Headers of the request', default: '2022-06-28' }, { label: 'Page ID', name: 'pageID', type: 'string', optional: false, description: 'ID of the page', show: { 'actions.pageOperation': ['getPage', 'updatePage', 'deletePage'] } }, { label: 'Database ID', name: 'databaseID', type: 'string', optional: false, description: 'ID of the database', show: { 'actions.databaseOperation': ['getDatabase', 'updateDatabase', 'queryDatabase'] } }, { label: 'Block ID', name: 'blockID', type: 'string', optional: false, description: 'ID of the block', show: { 'actions.blockOperation': ['getBlock', 'updateBlock', 'deleteBlock', 'appendBlockChildren', 'getBlockChildren'] } }, { label: 'Query String', name: 'queryString', type: 'string', optional: false, description: 'Name of the page (with your Notion Integration) to be searched', show: { 'actions.target': ['searchTarget'] } }, { label: 'Body', name: 'pageBody', type: 'json', description: 'Refer to the Notion API docs on how to structure the request body for page', show: { 'actions.pageOperation': ['createPage', 'updatePage'] }, placeholder: `{ "parent": { "database_id": "{{databaseID}}" }, "properties": { "Name": { "title": [ { "text": { "content": "New Media Article" } } ] } } }`, optional: false }, { label: 'Body', name: 'databaseBody', type: 'json', description: 'Refer to the Notion API docs on how to structure the request body for database', show: { 'actions.databaseOperation': ['createDatabase', 'updateDatabase', 'queryDatabase'] }, placeholder: `{ "parent": { "type": "page_id", "page_id": "{{pageID}}" }, "title": [ { "type": "text", "text": { "content": "Grocery List", } } ], "properties": { "Name": { "title": {} } } }`, optional: false }, { label: 'Body', name: 'blockBody', type: 'json', description: 'Refer to the Notion API docs on how to structure the request body for block', show: { 'actions.blockOperation': ['updateBlock', 'appendBlockChildren'] }, placeholder: `{ "paragraph": { "rich_text": [{ "type": "text", "text": { "content": "hello to you"} }] } }`, optional: false } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Notion Integration Token', name: 'notionApi' } ], default: 'notionApi' } ] as INodeParams[] } async run(nodeData: INodeData): Promise { const actionsData = nodeData.actions const inputParameters = nodeData.inputParameters const credentials = nodeData.credentials if (actionsData === undefined || credentials === undefined || inputParameters === undefined) { throw new Error('Required information missing') } const pageId = inputParameters.pageID const databaseId = inputParameters.databaseID const blockId = inputParameters.blockID const returnData: ICommonObject[] = [] const pageOperation = actionsData.pageOperation as string const databaseOperation = actionsData.databaseOperation as string const blockOperation = actionsData.blockOperation as string const target = actionsData.target as string const bearerToken = credentials.integrationToken as string //Request Body const pageBody = inputParameters.pageBody as string const databaseBody = inputParameters.databaseBody as string const blockBody = inputParameters.blockBody as string //Notion-Version for Headers const notionVersion = inputParameters.notionVersion as string //Search Data const queryString = inputParameters.queryString as string let responseData: any let requestPageBody, requestDatabaseBody, requestBlockBody: any = {} //JSON input params body if (pageBody) { requestPageBody = JSON.parse(pageBody.replace(/\s/g, ' ')) } if (databaseBody) { requestDatabaseBody = JSON.parse(databaseBody.replace(/\s/g, ' ')) } if (blockBody) { requestBlockBody = JSON.parse(blockBody.replace(/\s/g, ' ')) } //Search Endpoint if (target === 'searchTarget') { try { const axiosConfig: AxiosRequestConfig = { method: 'POST' as Method, url: `https://api.notion.com/v1/search`, headers: { Authorization: `Bearer ${bearerToken}`, 'Notion-Version': `${notionVersion}` }, data: { query: `${queryString}`, sort: { direction: 'ascending', timestamp: 'last_edited_time' } } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } } //Page Endpoints if (pageOperation === 'getPage') { try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://api.notion.com/v1/pages/${pageId}`, headers: { Authorization: `Bearer ${bearerToken}`, 'Notion-Version': `${notionVersion}` } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } } if (pageOperation === 'createPage') { try { const axiosConfig: AxiosRequestConfig = { method: 'POST' as Method, url: `https://api.notion.com/v1/pages`, headers: { Authorization: `Bearer ${bearerToken}`, 'Notion-Version': `${notionVersion}` } } axiosConfig.data = requestPageBody const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } } if (pageOperation === 'updatePage') { try { const axiosConfig: AxiosRequestConfig = { method: 'PATCH' as Method, url: `https://api.notion.com/v1/pages/${pageId}`, headers: { Authorization: `Bearer ${bearerToken}`, 'Notion-Version': `${notionVersion}` } } axiosConfig.data = requestPageBody const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } } if (pageOperation === 'deletePage') { try { const axiosConfig: AxiosRequestConfig = { method: 'DELETE' as Method, url: `https://api.notion.com/v1/blocks/${pageId}`, headers: { Authorization: `Bearer ${bearerToken}`, 'Notion-Version': `${notionVersion}` } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } } //Database Endpoints if (databaseOperation === 'getDatabase') { try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://api.notion.com/v1/databases/${databaseId}`, headers: { Authorization: `Bearer ${bearerToken}`, 'Notion-Version': `${notionVersion}` } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } } if (databaseOperation === 'createDatabase') { try { const axiosConfig: AxiosRequestConfig = { method: 'POST' as Method, url: `https://api.notion.com/v1/databases`, headers: { Authorization: `Bearer ${bearerToken}`, 'Notion-Version': `${notionVersion}` } } axiosConfig.data = requestDatabaseBody const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } } if (databaseOperation === 'updateDatabase') { try { const axiosConfig: AxiosRequestConfig = { method: 'PATCH' as Method, url: `https://api.notion.com/v1/databases/${databaseId}`, headers: { Authorization: `Bearer ${bearerToken}`, 'Notion-Version': `${notionVersion}` } } axiosConfig.data = requestDatabaseBody const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } } if (databaseOperation === 'queryDatabase') { try { const axiosConfig: AxiosRequestConfig = { method: 'POST' as Method, url: `https://api.notion.com/v1/databases/${databaseId}/query`, headers: { Authorization: `Bearer ${bearerToken}`, 'Notion-Version': `${notionVersion}` } } axiosConfig.data = requestDatabaseBody const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } } //Block Endpoints if (blockOperation === 'getBlock') { try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://api.notion.com/v1/blocks/${blockId}`, headers: { Authorization: `Bearer ${bearerToken}`, 'Notion-Version': `${notionVersion}` } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } } if (blockOperation === 'updateBlock') { try { const axiosConfig: AxiosRequestConfig = { method: 'PATCH' as Method, url: `https://api.notion.com/v1/blocks/${blockId}`, headers: { Authorization: `Bearer ${bearerToken}`, 'Notion-Version': `${notionVersion}` } } axiosConfig.data = requestBlockBody const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } } if (blockOperation === 'getBlockChildren') { try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://api.notion.com/v1/blocks/${blockId}/children`, headers: { Authorization: `Bearer ${bearerToken}`, 'Notion-Version': `${notionVersion}` } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } } if (blockOperation === 'appendBlockChildren') { try { const axiosConfig: AxiosRequestConfig = { method: 'PATCH' as Method, url: `https://api.notion.com/v1/blocks/${blockId}/children`, headers: { Authorization: `Bearer ${bearerToken}`, 'Notion-Version': `${notionVersion}` } } axiosConfig.data = requestBlockBody const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } } if (blockOperation === 'deleteBlock') { try { const axiosConfig: AxiosRequestConfig = { method: 'DELETE' as Method, url: `https://api.notion.com/v1/blocks/${blockId}`, headers: { Authorization: `Bearer ${bearerToken}`, 'Notion-Version': `${notionVersion}` } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: Notion } ================================================ FILE: packages/components/nodes/OpenAI/OpenAI.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' class OpenAI implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'OpenAI' this.name = 'openAI' this.icon = 'openai.png' this.type = 'action' this.category = 'Artificial Intelligence' this.version = 1.0 this.description = 'ChatGPT, image generation or text completion from prompt via OpenAI API' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Operation', name: 'operation', type: 'options', options: [ { label: 'ChatGPT', name: 'chatgpt' }, { label: 'Generate Image via text prompt', name: 'generateImage' }, { label: 'Generate Text Completion via text prompt', name: 'textCompletion' } ] } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'OpenAI API Key', name: 'openAIApi' } ], default: 'openAIApi' } ] as INodeParams[] this.inputParameters = [ { label: 'Model', name: 'model', type: 'asyncOptions', loadMethod: 'listModels', description: 'AI model to use.', default: 'text-davinci-003', show: { 'actions.operation': ['textCompletion'] } }, { label: 'Model', name: 'model', type: 'options', options: [ { label: 'gpt-3.5-turbo', name: 'gpt-3.5-turbo' }, { label: 'gpt-3.5-turbo-0301', name: 'gpt-3.5-turbo-0301' } ], description: 'ChatGPT model to use.', default: 'gpt-3.5-turbo', show: { 'actions.operation': ['chatgpt'] } }, { label: 'Text Prompt', name: 'prompt', type: 'string', default: '', placeholder: 'Write me a 250 words essay on fish', description: 'The prompt for chatpgt', show: { 'actions.operation': ['chatgpt'] } }, { label: 'Text Prompt', name: 'prompt', type: 'string', default: '', placeholder: 'Say this is a test', description: 'The prompt to generate completions for', show: { 'actions.operation': ['textCompletion'] } }, { label: 'Image Description', name: 'prompt', type: 'string', default: '', placeholder: 'Photograph of an astronaut riding a horse', description: 'Description of the image you want to generated. The maximum length is 1000 characters.', show: { 'actions.operation': ['generateImage'] } }, { label: 'Image Number', name: 'imageNumber', type: 'number', placeholder: '1', default: '1', description: 'The number of images to generate. Must be between 1 and 10.', optional: true, show: { 'actions.operation': ['generateImage'] } }, { label: 'Image Size', name: 'imageSize', type: 'options', description: 'The size of the generated images.', options: [ { label: '256x256', name: '256x256' }, { label: '512x512', name: '512x512' }, { label: '1024x1024', name: '1024x1024' } ], optional: true, default: '1024x1024', show: { 'actions.operation': ['generateImage'] } }, { label: 'Response Format', name: 'response_format', type: 'options', description: 'The format in which the generated images are returned.', options: [ { label: 'url', name: 'url' }, { label: 'b64_json', name: 'b64_json' } ], default: 'url', optional: true, show: { 'actions.operation': ['generateImage'] } } ] as INodeParams[] } loadMethods = { async listModels(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const credentials = nodeData.credentials if (credentials === undefined) return returnData try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://api.openai.com/v1/models`, headers: { 'Content-Type': 'application/json; charset=utf-8', Authorization: `Bearer ${credentials!.apiKey}` } } const response = await axios(axiosConfig) const responseData = response.data const models = responseData.data for (let i = 0; i < models.length; i += 1) { const data = { label: models[i].id, name: models[i].id } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } } } async run(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters const credentials = nodeData.credentials const actionssData = nodeData.actions if (inputParametersData === undefined || actionssData === undefined) { throw new Error('Required data missing') } if (credentials === undefined) { throw new Error('Missing credential') } const returnData: ICommonObject[] = [] const operation = actionssData.operation as string const model = inputParametersData.model as string const prompt = inputParametersData.prompt as string const imageNumber = inputParametersData.imageNumber as string const imageSize = inputParametersData.imageSize as string const response_format = inputParametersData.response_format as string let responseData: any let url = '' switch (operation) { case 'generateImage': url = 'https://api.openai.com/v1/images/generations' break case 'textCompletion': url = 'https://api.openai.com/v1/completions' break case 'chatgpt': url = 'https://api.openai.com/v1/chat/completions' break } const data = { prompt } as any if (imageNumber) data.n = parseInt(imageNumber, 10) if (imageSize) data.size = imageSize if (response_format) data.response_format = response_format if (model) data.model = model if (operation === 'chatgpt') { delete data.prompt data.temperature = 0.8 data.top_p = 1.0 data.presence_penalty = 1.0 data.messages = [ { role: 'system', content: `You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible. Current date: ${ new Date().toISOString().split('T')[0] }` }, { role: 'user', content: prompt } ] } try { const axiosConfig: AxiosRequestConfig = { method: 'POST' as Method, url, data, headers: { 'Content-Type': 'application/json; charset=utf-8', Authorization: `Bearer ${credentials!.apiKey}` } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: OpenAI } ================================================ FILE: packages/components/nodes/Opensea/Opensea.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' import axios, { AxiosRequestConfig, AxiosRequestHeaders, Method } from 'axios' import { retrieveACollection, retrieveAContract, retrieveAnAsset, retrieveAssets, retrieveBundles, retrieveCollections, retrieveEvents } from './extendedParameters' class OpenSea implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'OpenSea' this.name = 'opensea' this.icon = 'opensea.svg' this.type = 'action' this.category = 'NFT' this.version = 1.0 this.description = 'Query OpenSea information' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Operation', name: 'operation', type: 'options', options: [ { label: 'Retrieve Assets', name: 'retrieveAssets', description: 'Returns a set of NFTs.' }, { label: 'Retrieve Events', name: 'retrieveEvents', description: 'Returns a list of Events.' }, { label: 'Retrieve Collections', name: 'retrieveCollections', description: 'Returns a list of collections supported and vetted by OpenSea.' }, { label: 'Retrieve Bundles', name: 'retrieveBundles', description: 'Returns a list of bundles. Bundles are groups of NFTs for sale on OpenSea.' }, { label: 'Retrieve An Asset', name: 'retrieveAnAsset', description: 'Fetch information about a single NFT, based on its contract address and token ID.' }, { label: 'Retrieve A Contract', name: 'retrieveAContract', description: 'Fetch detailed information about a specified contract.' }, { label: 'Retrieve A Collection', name: 'retrieveACollection', description: 'Retrieve more in-depth information about an individual collection, including real time statistics such as floor price.' }, { label: 'Retrieve Collection Stats', name: 'retrieveCollectionStats', description: 'Fetch stats for a specific collection, including real-time floor price data.' } ], default: '' }, { label: 'Event Type', name: 'event_type', type: 'options', options: [ { label: 'Auctions', name: 'created' }, { label: 'Sales', name: 'successful' }, { label: 'Transfer', name: 'transfer' }, { label: 'Approve', name: 'approve' }, { label: 'Bid Entered', name: 'bid_entered' }, { label: 'Bid Withdrawn', name: 'bid_withdrawn' }, { label: 'Cancelled', name: 'cancelled' }, { label: 'All Events', name: 'all' } ], default: '', description: 'The event type to filter', show: { 'actions.operation': ['retrieveEvents'] } }, { label: 'Auction Type', name: 'auction_type', type: 'options', options: [ { label: 'Sell to the highest bidder', name: 'english', description: 'The highest bid wins at the end' }, { label: 'Sell with a declining price', name: 'dutch', description: 'The price falls until someone purchases the item' }, { label: 'CryptoPunks Auctions', name: 'min-price' } ], default: '', optional: true, description: 'Filter by an auction type', show: { 'actions.operation': ['retrieveEvents'], 'actions.event_type': ['created'] } }, { label: 'Environment', name: 'environment', type: 'options', description: 'Environment to execute operation: Test or Main', options: [ { label: 'TEST', name: 'https://testnets-api.opensea.io/api/v1', description: 'Testnet: https://testnets.opensea.io/' }, { label: 'MAIN', name: 'https://api.opensea.io/api/v1', description: 'Mainnet: https://opensea.io/' } ], default: '' } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'OpenSea API Key', name: 'openSeaApi', description: 'How to get API key: https://docs.opensea.io/reference/request-an-api-key' } ], default: 'openSeaApi', show: { 'actions.environment': ['https://api.opensea.io/api/v1'] } } ] as INodeParams[] this.inputParameters = [ ...retrieveAssets, ...retrieveEvents, ...retrieveCollections, ...retrieveBundles, ...retrieveAnAsset, ...retrieveAContract, ...retrieveACollection ] as INodeParams[] } async run(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters const actionData = nodeData.actions if (actionData === undefined) { throw new Error('Required data missing') } const operation = actionData.operation as string const baseURL = actionData.environment as string const returnData: ICommonObject[] = [] let responseData: any let url = '' const queryParameters: ICommonObject = {} const method: Method = 'GET' const headers: AxiosRequestHeaders = { 'Content-Type': 'application/json' } if (baseURL === 'https://api.opensea.io/api/v1') { // Mainnet const credentials = nodeData.credentials if (credentials === undefined) { throw new Error('Missing credentials') } const apiKey = credentials!.apiKey as string headers['X-API-KEY'] = apiKey } try { if (operation === 'retrieveAssets') { url = `${baseURL}/assets` const owner = inputParametersData?.owner as string if (owner) queryParameters['owner'] = owner const token_ids_string = inputParametersData?.token_ids as string if (token_ids_string) { const token_ids = JSON.parse(token_ids_string.replace(/\s/g, '')) if (token_ids.length) queryParameters['token_ids'] = token_ids } const asset_contract_address = inputParametersData?.asset_contract_address as string if (asset_contract_address) queryParameters['asset_contract_address'] = asset_contract_address const asset_contract_addresses_string = inputParametersData?.asset_contract_addresses as string if (asset_contract_addresses_string) { const asset_contract_addresses = JSON.parse(asset_contract_addresses_string.replace(/\s/g, '')) if (asset_contract_addresses.length) queryParameters['asset_contract_addresses'] = asset_contract_addresses } const order_by = inputParametersData?.order_by as string if (order_by) queryParameters['order_by'] = order_by const order_direction = inputParametersData?.order_direction as string if (order_direction) queryParameters['order_direction'] = order_direction const offset = inputParametersData?.offset as number if (offset) queryParameters['offset'] = offset const limit = inputParametersData?.limit as number if (limit) queryParameters['limit'] = limit const collection = inputParametersData?.collection as string if (collection) queryParameters['collection'] = collection const include_orders = inputParametersData?.include_orders as boolean if (include_orders) queryParameters['include_orders'] = include_orders } else if (operation === 'retrieveEvents') { url = `${baseURL}/events` const event_type = actionData.event_type as string if (event_type && event_type !== 'all') queryParameters['event_type'] = event_type const auction_type = actionData.auction_type as string if (auction_type) queryParameters['auction_type'] = auction_type const asset_contract_address = inputParametersData?.asset_contract_address as string if (asset_contract_address) queryParameters['asset_contract_address'] = asset_contract_address const collection_slug = inputParametersData?.collection_slug as string if (collection_slug) queryParameters['collection_slug'] = collection_slug const token_id = inputParametersData?.token_id as number if (token_id) queryParameters['token_id'] = token_id const account_address = inputParametersData?.account_address as string if (account_address) queryParameters['account_address'] = account_address const only_opensea = inputParametersData?.only_opensea as boolean if (only_opensea) queryParameters['only_opensea'] = only_opensea const offset = inputParametersData?.offset as number if (offset) queryParameters['offset'] = offset const limit = inputParametersData?.limit as number if (limit) queryParameters['limit'] = limit const occurred_before = Date.parse(inputParametersData?.occurred_before as string) if (occurred_before) queryParameters['occurred_before'] = occurred_before const occurred_after = Date.parse(inputParametersData?.occurred_after as string) if (occurred_after) queryParameters['occurred_after'] = occurred_after } else if (operation === 'retrieveCollections') { url = `${baseURL}/collections` const asset_owner = inputParametersData?.asset_owner as string if (asset_owner) queryParameters['asset_owner'] = asset_owner const offset = inputParametersData?.offset as number if (offset) queryParameters['offset'] = offset const limit = inputParametersData?.limit as number if (limit) queryParameters['limit'] = limit } else if (operation === 'retrieveBundles') { url = `${baseURL}/bundles` const on_sale = inputParametersData?.on_sale as boolean if (on_sale) queryParameters['on_sale'] = on_sale const owner = inputParametersData?.owner as string if (owner) queryParameters['owner'] = owner const token_ids_string = inputParametersData?.token_ids as string if (token_ids_string) { const token_ids = JSON.parse(token_ids_string.replace(/\s/g, '')) if (token_ids.length) queryParameters['token_ids'] = token_ids } const asset_contract_address = inputParametersData?.asset_contract_address as string if (asset_contract_address) queryParameters['asset_contract_address'] = asset_contract_address const asset_contract_addresses_string = inputParametersData?.asset_contract_addresses as string if (asset_contract_addresses_string) { const asset_contract_addresses = JSON.parse(asset_contract_addresses_string.replace(/\s/g, '')) if (asset_contract_addresses.length) queryParameters['asset_contract_addresses'] = asset_contract_addresses } const offset = inputParametersData?.offset as number if (offset) queryParameters['offset'] = offset const limit = inputParametersData?.limit as number if (limit) queryParameters['limit'] = limit } else if (operation === 'retrieveAnAsset') { const token_id = inputParametersData!.token_id as string const asset_contract_address = inputParametersData!.asset_contract_address as string url = `${baseURL}/asset/${asset_contract_address}/${token_id}` } else if (operation === 'retrieveAContract') { const asset_contract_address = inputParametersData!.asset_contract_address as string url = `${baseURL}/asset_contract/${asset_contract_address}` } else if (operation === 'retrieveACollection') { const collection_slug = inputParametersData!.collection_slug as string url = `${baseURL}/collection/${collection_slug}` } else if (operation === 'retrieveCollectionStats') { const collection_slug = inputParametersData!.collection_slug as string url = `${baseURL}/collection/${collection_slug}/stats` } const axiosConfig: AxiosRequestConfig = { method, url, headers } if (Object.keys(queryParameters).length > 0) { axiosConfig.params = queryParameters axiosConfig.paramsSerializer = (params) => serializeQueryParams(params, true) } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: OpenSea } ================================================ FILE: packages/components/nodes/Opensea/OpenseaEventTrigger.ts ================================================ import { CronJob } from 'cron' import { ICommonObject, ICronJobs, INode, INodeData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' import EventEmitter from 'events' import { eventsTriggerParams } from './extendedParameters' import axios, { AxiosRequestConfig, AxiosRequestHeaders, Method } from 'axios' class OpenSeaEventTrigger extends EventEmitter implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] cronJobs: ICronJobs constructor() { super() this.label = 'OpenSea Event Trigger' this.name = 'openSeaEventTrigger' this.icon = 'opensea.svg' this.type = 'trigger' this.category = 'NFT' this.version = 1.0 this.description = 'Start workflow whenever OpenSea event occurs' this.incoming = 0 this.outgoing = 1 this.cronJobs = {} this.actions = [ { label: 'Event Type', name: 'event_type', type: 'options', options: [ { label: 'New Auctions', name: 'created' }, { label: 'New Sales', name: 'successful' }, { label: 'New Transfer', name: 'transfer' }, { label: 'New Approve', name: 'approve' }, { label: 'New Bid Entered', name: 'bid_entered' }, { label: 'Bid Withdrawn', name: 'bid_withdrawn' }, { label: 'Cancelled', name: 'cancelled' } ], default: '', description: 'The event type to filter' }, { label: 'Auction Type', name: 'auction_type', type: 'options', options: [ { label: 'Sell to the highest bidder', name: 'english', description: 'The highest bid wins at the end' }, { label: 'Sell with a declining price', name: 'dutch', description: 'The price falls until someone purchases the item' }, { label: 'CryptoPunks Auctions', name: 'min-price' } ], default: '', optional: true, description: 'Filter by an auction type', show: { 'actions.event_type': ['created'] } }, { label: 'Environment', name: 'environment', type: 'options', description: 'Environment to listen to event: Test or Main', options: [ { label: 'TEST', name: 'https://testnets-api.opensea.io/api/v1', description: 'Testnet: https://testnets.opensea.io/' }, { label: 'MAIN', name: 'https://api.opensea.io/api/v1', description: 'Mainnet: https://opensea.io/' } ], default: '' } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'OpenSea API Key', name: 'openSeaApi', description: 'How to get API key: https://docs.opensea.io/reference/request-an-api-key' } ], default: 'openSeaApi', show: { 'actions.environment': ['https://api.opensea.io/api/v1'] } } ] as INodeParams[] this.inputParameters = [ ...eventsTriggerParams, { label: 'Polling Time', name: 'pollTime', type: 'options', description: 'How often should we keep checking the event', options: [ { label: 'Every 15 secs', name: '15s' }, { label: 'Every 30 secs', name: '30s' }, { label: 'Every 1 min', name: '1min' }, { label: 'Every 5 min', name: '5min' }, { label: 'Every 10 min', name: '10min' } ], default: '30s' } ] as INodeParams[] } async runTrigger(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters const actionData = nodeData.actions if (actionData === undefined) { throw new Error('Required data missing') } const baseURL = actionData.environment as string let url = '' const queryParameters: ICommonObject = {} const method: Method = 'GET' const headers: AxiosRequestHeaders = { 'Content-Type': 'application/json' } if (baseURL === 'https://api.opensea.io/api/v1') { // Mainnet const credentials = nodeData.credentials if (credentials === undefined) { throw new Error('Missing credentials') } const apiKey = credentials!.apiKey as string headers['X-API-KEY'] = apiKey } const emitEventKey = nodeData.emitEventKey as string const pollTime = (inputParametersData?.pollTime as string) || '30s' const cronTimes: string[] = [] if (pollTime === '15s') { cronTimes.push(`*/15 * * * * *`) } else if (pollTime === '30s') { cronTimes.push(`*/30 * * * * *`) } else if (pollTime === '1min') { cronTimes.push(`*/1 * * * *`) } else if (pollTime === '5min') { cronTimes.push(`*/5 * * * *`) } else if (pollTime === '10min') { cronTimes.push(`*/10 * * * *`) } function mapEventName(event: string) { switch (event) { case 'created': return 'New Auctions' case 'successful': return 'New Sales' case 'transfer': return 'New Transfer' case 'approve': return 'New Approve' case 'bid_entered': return 'New Bid Entered' case 'bid_withdrawn': return 'Bid Withdrawn' case 'cancelled': return 'Cancelled' default: return '' } } async function executeEventAPI(lastEventTimestamp?: any) { // Get initial data url = `${baseURL}/events` const event_type = actionData?.event_type as string if (event_type) queryParameters['event_type'] = event_type const auction_type = actionData?.auction_type as string if (auction_type) queryParameters['auction_type'] = auction_type const asset_contract_address = inputParametersData?.asset_contract_address as string if (asset_contract_address) queryParameters['asset_contract_address'] = asset_contract_address const collection_slug = inputParametersData?.collection_slug as string if (collection_slug) queryParameters['collection_slug'] = collection_slug const token_id = inputParametersData?.token_id as number if (token_id) queryParameters['token_id'] = token_id const account_address = inputParametersData?.account_address as string if (account_address) queryParameters['account_address'] = account_address const only_opensea = inputParametersData?.only_opensea as boolean if (only_opensea) queryParameters['only_opensea'] = only_opensea if (lastEventTimestamp) queryParameters['occurred_after'] = lastEventTimestamp const axiosConfig: AxiosRequestConfig = { method, url, headers } if (Object.keys(queryParameters).length > 0) { axiosConfig.params = queryParameters axiosConfig.paramsSerializer = (params) => serializeQueryParams(params, true) } return await axios(axiosConfig) } try { // Get initial data let lastEventTimestamp: any const response = await executeEventAPI() const responseData = response.data if (responseData.asset_events && responseData.asset_events.length) lastEventTimestamp = responseData.asset_events[0].event_timestamp // Trigger when cron job hits const executeTrigger = async () => { const newResponse = await executeEventAPI(lastEventTimestamp) const newResponseData = newResponse.data if (newResponseData.asset_events && newResponseData.asset_events.length) { lastEventTimestamp = newResponseData.asset_events[0].event_timestamp const returnItem = { event: mapEventName(actionData?.event_type as string), eventData: newResponseData } this.emit(emitEventKey, returnNodeExecutionData(returnItem)) } } // Start the cron-jobs if (Object.prototype.hasOwnProperty.call(this.cronJobs, emitEventKey)) { for (const cronTime of cronTimes) { // Automatically start the cron job this.cronJobs[emitEventKey].push(new CronJob(cronTime, executeTrigger, undefined, true)) } } else { for (const cronTime of cronTimes) { // Automatically start the cron job this.cronJobs[emitEventKey] = [new CronJob(cronTime, executeTrigger, undefined, true)] } } } catch (e) { throw handleErrorMessage(e) } } async removeTrigger(nodeData: INodeData): Promise { const emitEventKey = nodeData.emitEventKey as string if (Object.prototype.hasOwnProperty.call(this.cronJobs, emitEventKey)) { const cronJobs = this.cronJobs[emitEventKey] for (const cronJob of cronJobs) { cronJob.stop() } this.removeAllListeners(emitEventKey) } } } module.exports = { nodeClass: OpenSeaEventTrigger } ================================================ FILE: packages/components/nodes/Opensea/extendedParameters.ts ================================================ import { INodeParams } from '../../src' export const retrieveAssets = [ { label: 'Owner', name: 'owner', type: 'string', default: '', optional: true, description: 'The address of the owner of the assets', show: { 'actions.operation': ['retrieveAssets'] } }, { label: 'Token Ids', name: 'token_ids', type: 'json', placeholder: '["1", "2"]', optional: true, description: 'An array of token IDs to search for', show: { 'actions.operation': ['retrieveAssets'] } }, { label: 'Asset Contract Address', name: 'asset_contract_address', type: 'string', default: '', optional: true, description: 'The NFT contract address for the assets', show: { 'actions.operation': ['retrieveAssets'] } }, { label: 'Asset Contract Addresses', name: 'asset_contract_addresses', type: 'json', placeholder: '["0xa", "0xb"]', optional: true, description: 'An array of contract addresses to search for', show: { 'actions.operation': ['retrieveAssets'] } }, { label: 'Order By', name: 'order_by', type: 'options', options: [ { label: 'Sale Date', name: 'sale_date', description: `Last sale's transaction's timestamp` }, { label: 'Sale Count ', name: 'sale_count', description: `Number of sales` }, { label: 'Sale Price ', name: 'sale_price', description: `Last sale's total_price` } ], default: '', optional: true, description: 'How to order the assets returned. By default, the API returns the fastest ordering. ', show: { 'actions.operation': ['retrieveAssets'] } }, { label: 'Order Direction', name: 'order_direction', type: 'options', options: [ { label: 'Descending', name: 'desc' }, { label: 'Ascending', name: 'asc' } ], default: '', optional: true, show: { 'actions.operation': ['retrieveAssets'] } }, { label: 'Offset', name: 'offset', type: 'number', default: '', optional: true, description: 'Offset for pagination', show: { 'actions.operation': ['retrieveAssets'] } }, { label: 'Limit', name: 'limit', type: 'number', default: 20, optional: true, description: 'Limit for pagination. Defaults to 20, capped at 50.', show: { 'actions.operation': ['retrieveAssets'] } }, { label: 'Collection', name: 'collection', type: 'string', default: '', optional: true, description: 'Limit responses to members of a collection. Case sensitive and must match the collection slug exactly. Will return all assets from all contracts in a collection.', show: { 'actions.operation': ['retrieveAssets'] } }, { label: 'Include Orders', name: 'include_orders', type: 'boolean', default: false, optional: true, description: 'A flag determining if order information should be included in the response.', show: { 'actions.operation': ['retrieveAssets'] } } ] as INodeParams[] export const retrieveEvents = [ { label: 'Asset Contract Address', name: 'asset_contract_address', type: 'string', default: '', optional: true, description: 'The NFT contract address for the assets for which to show events', show: { 'actions.operation': ['retrieveEvents'] } }, { label: 'Collection Slug', name: 'collection_slug', type: 'string', default: '', optional: true, description: 'Events from a collection. For instance: if a collection url is: https://opensea.io/collection/my-collection-1, collection slug is my-collection-1.', show: { 'actions.operation': ['retrieveEvents'] } }, { label: 'Token Id', name: 'token_id', type: 'number', default: '', optional: true, description: `The token's id to optionally filter by`, show: { 'actions.operation': ['retrieveEvents'] } }, { label: 'Account Address', name: 'account_address', type: 'string', default: '', optional: true, description: `A user account's wallet address to filter for events on an account`, show: { 'actions.operation': ['retrieveEvents'] } }, { label: 'Only Opensea', name: 'only_opensea', type: 'boolean', default: false, optional: true, description: 'Restrict to events on OpenSea auctions', show: { 'actions.operation': ['retrieveEvents'] } }, { label: 'Offset', name: 'offset', type: 'number', default: '', description: 'Offset for pagination', optional: true, show: { 'actions.operation': ['retrieveEvents'] } }, { label: 'Limit', name: 'limit', type: 'number', default: 20, optional: true, description: 'Limit for pagination', show: { 'actions.operation': ['retrieveEvents'] } }, { label: 'Occurred Before', name: 'occurred_before', type: 'date', optional: true, description: 'Only show events listed before this date.', show: { 'actions.operation': ['retrieveEvents'] } }, { label: 'Occurred After', name: 'occurred_after', type: 'date', optional: true, description: 'Only show events listed after this date.', show: { 'actions.operation': ['retrieveEvents'] } } ] as INodeParams[] export const retrieveCollections = [ { label: 'Asset Owner', name: 'asset_owner', type: 'string', default: '', optional: true, description: `A wallet address. If specified, will return collections where the owner owns at least one asset belonging to smart contracts in the collection.`, show: { 'actions.operation': ['retrieveCollections'] } }, { label: 'Offset', name: 'offset', type: 'number', default: '', description: 'Offset for pagination', optional: true, show: { 'actions.operation': ['retrieveCollections'] } }, { label: 'Limit', name: 'limit', type: 'number', default: 20, optional: true, description: 'Limit for pagination', show: { 'actions.operation': ['retrieveCollections'] } } ] as INodeParams[] export const retrieveBundles = [ { label: 'On Sale', name: 'on_sale', type: 'boolean', default: false, optional: true, description: `If set to true, only show bundles currently on sale. If set to false, only show bundles that have been sold or cancelled.`, show: { 'actions.operation': ['retrieveBundles'] } }, { label: 'Owner', name: 'owner', type: 'string', default: '', description: 'Account address of the owner of a bundle (and all assets within)', optional: true, show: { 'actions.operation': ['retrieveBundles'] } }, { label: 'Asset Contract Address', name: 'asset_contract_address', type: 'string', default: '', optional: true, description: 'Contract address of all the assets in a bundle. Only works for homogenous bundles, not mixed ones.', show: { 'actions.operation': ['retrieveBundles'] } }, { label: 'Asset Contract Addresses', name: 'asset_contract_addresses', type: 'json', placeholder: '["0xa", "0xb"]', optional: true, description: 'An array of contract addresses to search for', show: { 'actions.operation': ['retrieveBundles'] } }, { label: 'Token Ids', name: 'token_ids', type: 'json', placeholder: '["1", "2"]', optional: true, description: 'A list of token IDs for showing only bundles with at least one of the token IDs in the list', show: { 'actions.operation': ['retrieveBundles'] } }, { label: 'Offset', name: 'offset', type: 'number', default: '', description: 'Offset for pagination', optional: true, show: { 'actions.operation': ['retrieveBundles'] } }, { label: 'Limit', name: 'limit', type: 'number', default: 20, optional: true, description: 'Limit for pagination', show: { 'actions.operation': ['retrieveBundles'] } } ] as INodeParams[] export const retrieveAnAsset = [ { label: 'Asset Contract Address', name: 'asset_contract_address', type: 'string', default: '', description: 'The NFT contract address for the assets', show: { 'actions.operation': ['retrieveAnAsset'] } }, { label: 'Token Id', name: 'token_id', type: 'string', default: '', description: 'Token ID for this item', show: { 'actions.operation': ['retrieveAnAsset'] } } ] as INodeParams[] export const retrieveAContract = [ { label: 'Asset Contract Address', name: 'asset_contract_address', type: 'string', default: '', description: 'The NFT contract address for the assets', show: { 'actions.operation': ['retrieveAContract'] } } ] as INodeParams[] export const retrieveACollection = [ { label: 'Collection Slug', name: 'collection_slug', type: 'string', default: '', description: 'For instance: if a collection url is: https://opensea.io/collection/my-collection-1, collection slug is my-collection-1.', show: { 'actions.operation': ['retrieveACollection', 'retrieveCollectionStats'] } } ] as INodeParams[] export const eventsTriggerParams = [ { label: 'Listen From', name: 'listenFrom', type: 'options', options: [ { label: 'From A NFT', name: 'fromANFT' }, { label: 'From A Collection', name: 'fromACollection' } ], default: 'fromANFT', description: 'Listen event from a NFT or collection' }, { label: 'Asset Contract Address', name: 'asset_contract_address', type: 'string', default: '', description: 'The NFT contract address for the assets for which to show events', show: { 'inputParameters.listenFrom': ['fromANFT'] } }, { label: 'Token Id', name: 'token_id', type: 'number', default: '', description: `The token's id of NFT`, show: { 'inputParameters.listenFrom': ['fromANFT'] } }, { label: 'Collection Slug', name: 'collection_slug', type: 'string', default: '', optional: true, description: 'Events from a collection. For instance: if a collection url is: https://opensea.io/collection/my-collection-1, collection slug is my-collection-1.', show: { 'inputParameters.listenFrom': ['fromACollection'] } }, { label: 'Account Address', name: 'account_address', type: 'string', default: '', optional: true, description: `A user account's wallet address to filter for events on an account` }, { label: 'Only Opensea', name: 'only_opensea', type: 'boolean', default: false, optional: true, description: 'Restrict to events on OpenSea auctions' } ] as INodeParams[] ================================================ FILE: packages/components/nodes/OptimismScan/OptimismScan.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' import { OPERATIONS, SORT_BY, GET_ETH_BALANCE, GET_ETH_BALANCE_MULTI, GET_NORMAL_TRANSACTIONS, GET_ERC20_TOKEN_TRANSFER, GET_DEPOSIT, GET_WITHDRAWAL, GET_CONTRACT_ABI, GET_CONTRACT_SOURCE_CODE, GET_ERC20_TOKEN_SUPPLY, GET_ERC20_TOKEN_ACCOUNT_BALANCE } from './constants' class OptimismScan implements INode { // properties label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number // parameter actions: INodeParams[] credentials?: INodeParams[] networks?: INodeParams[] inputParameters?: INodeParams[] constructor() { // properties this.label = 'Optimism Scan' this.name = 'optimismscan' this.icon = 'optimismScan.svg' this.type = 'action' this.category = 'Block Explorer' this.version = 1.0 this.description = 'Optimism Public API' this.incoming = 1 this.outgoing = 1 // parameter this.actions = [ { label: 'API', name: 'api', type: 'options', description: 'Choose the API to execute', options: [ // Account { label: 'Get ETH Balance', name: GET_ETH_BALANCE.name, description: 'Get ETH Balance for a single Address' }, { label: 'Get ETH Balance Multi', name: GET_ETH_BALANCE_MULTI.name, description: 'Get ETH Balance for multiple Addresses in a single call' }, { label: 'Get ERC20 Token Transfer Events', name: GET_ERC20_TOKEN_TRANSFER.name, description: 'Get a list of "ERC-20 - Token Transfer Events" by Address' }, { label: 'Get Deposit History', name: GET_DEPOSIT.name, description: 'Get list of L1 Deposits done by Address' }, { label: 'Get Withdrawal History', name: GET_WITHDRAWAL.name, description: 'Get list of L2 Withdrawals done by Address' }, { label: 'Get Contract ABI', name: GET_CONTRACT_ABI.name, description: 'Get Contract ABI for Verified Contract Source Codes' }, { label: 'Get Contract Source Code', name: GET_CONTRACT_SOURCE_CODE.name, description: 'Get Contract Source Code for Verified Contract Source Codes. (Replace the address with the actual contract address)' }, { label: 'Get ERC-20 Token Total Supply', name: GET_ERC20_TOKEN_SUPPLY.name, description: 'Get ERC-20 Token Total Supply by Contract Address' }, { label: 'Get ERC-20 Token Account Balance', name: GET_ERC20_TOKEN_ACCOUNT_BALANCE.name, description: 'Get ERC-20 Token Account Balance by Contract Address and Address' } ], default: GET_ETH_BALANCE.name } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'OptimismScan API Key', name: 'optimisticEtherscanApi' } ], default: 'optimisticEtherscanApi' } ] as INodeParams[] this.inputParameters = [ { label: 'Address', name: 'address', type: 'string', show: { 'actions.api': [ GET_ETH_BALANCE.name, GET_NORMAL_TRANSACTIONS.name, GET_ERC20_TOKEN_TRANSFER.name, GET_DEPOSIT.name, GET_WITHDRAWAL.name, GET_CONTRACT_ABI.name, GET_CONTRACT_SOURCE_CODE.name, GET_ERC20_TOKEN_ACCOUNT_BALANCE.name ] } }, { label: 'Addresses', name: 'addresses', type: 'array', array: [ { label: 'Address', name: 'address', type: 'string' } ], show: { 'actions.api': [GET_ETH_BALANCE_MULTI.name] } }, { label: 'Tag', name: 'tag', type: 'options', options: [{ label: 'latest', name: 'latest' }], default: 'latest', show: { 'actions.api': [GET_ETH_BALANCE.name, GET_ETH_BALANCE_MULTI.name, GET_ERC20_TOKEN_ACCOUNT_BALANCE.name] } }, { label: 'Start Block', name: 'startBlock', type: 'number', optional: true, description: 'the block number to start searching for transactions', show: { 'actions.api': [GET_NORMAL_TRANSACTIONS.name, GET_ERC20_TOKEN_TRANSFER.name] }, default: 0 }, { label: 'End Block', name: 'endBlock', type: 'number', optional: true, description: 'the block number to stop searching for transactions', show: { 'actions.api': [GET_NORMAL_TRANSACTIONS.name, GET_ERC20_TOKEN_TRANSFER.name] } }, { label: 'Page', name: 'page', type: 'number', optional: true, description: 'the page number, if pagination is enabled', show: { 'actions.api': [GET_NORMAL_TRANSACTIONS.name, GET_ERC20_TOKEN_TRANSFER.name] }, default: 1 }, { label: 'Offset', name: 'offset', type: 'number', optional: true, description: 'the number of transactions displayed per page', show: { 'actions.api': [GET_NORMAL_TRANSACTIONS.name, GET_ERC20_TOKEN_TRANSFER.name] }, default: 10 }, { label: 'Sort By', name: 'sortBy', type: 'options', options: SORT_BY, optional: true, show: { 'actions.api': [GET_NORMAL_TRANSACTIONS.name, GET_ERC20_TOKEN_TRANSFER.name, GET_DEPOSIT.name] }, default: 'desc' }, { label: 'Contract Address [Optional]', name: 'contractAddressOptional', type: 'string', description: 'the contract address of the ERC-20 token', optional: true, show: { 'actions.api': [GET_ERC20_TOKEN_TRANSFER.name] } }, { label: 'Contract Address', name: 'contractAddress', type: 'string', description: 'the contract address of the ERC-20 token', show: { 'actions.api': [GET_ERC20_TOKEN_SUPPLY.name, GET_ERC20_TOKEN_ACCOUNT_BALANCE.name] } }, { label: 'Token Address', name: 'tokenAddress', type: 'string', description: 'the contract address of the ERC-20 token', optional: true, show: { 'actions.api': [GET_DEPOSIT.name] } } ] as INodeParams[] } getBaseParams(api: string) { const operation = OPERATIONS.filter(({ name }) => name === api)[0] return { module: operation.module, action: operation.action } } async run(nodeData: INodeData): Promise { const url = 'https://api-optimistic.etherscan.io/api' const { actions, inputParameters, credentials } = nodeData if (actions === undefined || inputParameters === undefined || credentials === undefined) { throw new Error('Required data missing') } const api = actions.api as string const apiKey = credentials.apiKey as string const singleAddress = inputParameters.address as string const multiAddresses = (inputParameters.addresses as ICommonObject[]) || [] const startblock = inputParameters.startBlock as number const endblock = inputParameters.endBlock as number const page = inputParameters.page as number const offset = inputParameters.offset as number const sort = inputParameters.sortBy as string const tag = inputParameters.tag as string const contractAddress = inputParameters.contractAddress as string const tokenAddress = inputParameters.tokenAddress as string const contractAddressOptional = inputParameters.contractAddressOptional || undefined const { module, action } = this.getBaseParams(api) const address = singleAddress || multiAddresses.map(({ address }) => address).join(',') const contractaddress = contractAddress ? contractAddress : contractAddressOptional const queryParameters = { module, action, address, tag, apiKey, startblock, endblock, page, offset, sort, tokenAddress, contractaddress } const returnData: ICommonObject[] = [] let responseData: any try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url, params: queryParameters, paramsSerializer: (params) => serializeQueryParams(params), headers: { 'Content-Type': 'application/json' } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: OptimismScan } ================================================ FILE: packages/components/nodes/OptimismScan/constants.ts ================================================ // Account export const GET_ETH_BALANCE = { name: 'getEthBalance', module: 'account', action: 'balance' } export const GET_ETH_BALANCE_MULTI = { name: 'getEthBalanceMulti', module: 'account', action: 'balancemulti' } export const GET_NORMAL_TRANSACTIONS = { name: 'getNormalTransactions', module: 'account', action: 'txlist' } export const GET_ERC20_TOKEN_TRANSFER = { name: 'getErc20TokenTransfer', module: 'account', action: 'tokentx' } export const GET_DEPOSIT = { name: 'getDeposit', module: 'account', action: 'getdeposittxs' } export const GET_WITHDRAWAL = { name: 'getWithdrawal', module: 'account', action: 'getwithdrawaltxs' } // Contracts export const GET_CONTRACT_ABI = { name: 'getContractAbi', module: 'contract', action: 'getabi' } export const GET_CONTRACT_SOURCE_CODE = { name: 'getContractSourceCode', module: 'contract', action: 'getsourcecode' } // Tokens export const GET_ERC20_TOKEN_SUPPLY = { name: 'getErc20TokenSupply', module: 'stats', action: 'tokensupply' } export const GET_ERC20_TOKEN_ACCOUNT_BALANCE = { name: 'getErc20TokenAccountBalance', module: 'account', action: 'tokenbalance' } export const OPERATIONS = [ GET_ETH_BALANCE, GET_ETH_BALANCE_MULTI, GET_NORMAL_TRANSACTIONS, GET_ERC20_TOKEN_TRANSFER, GET_DEPOSIT, GET_WITHDRAWAL, GET_CONTRACT_ABI, GET_CONTRACT_SOURCE_CODE, GET_ERC20_TOKEN_SUPPLY, GET_ERC20_TOKEN_ACCOUNT_BALANCE ] as const export const SORT_BY = [ { label: 'Desc', name: 'desc' }, { label: 'Asc', name: 'asc' } ] ================================================ FILE: packages/components/nodes/PancakeSwap/PancakeSwap.ts ================================================ import { ethers } from 'ethers' import { ICommonObject, IDbCollection, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, IWallet, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import { networkExplorers, binanceNetworkProviders, CHAIN_ID, NETWORK, getNetworkProvider, NETWORK_PROVIDER } from '../../src/ChainNetwork' import axios, { AxiosRequestConfig, Method } from 'axios' import { UniswapPair, UniswapPairSettings, UniswapVersion } from 'simple-uniswap-sdk' import { IToken, nativeTokens } from './extendedTokens' import IWBNB from './abis/WBNB.json' class PancakeSwap implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] actions?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'PancakeSwap' this.name = 'pancakeSwap' this.icon = 'pancakeswap.png' this.type = 'action' this.category = 'Decentralized Finance' this.version = 1.0 this.description = 'Execute PancakeSwap operations' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Operation', name: 'operation', type: 'options', options: [ { label: 'Swap Tokens', name: 'swapTokens' }, { label: 'Custom Query', name: 'customQuery', description: 'Custom subgraph query to retrieve more information. https://github.com/pancakeswap/pancake-subgraph' } ], default: 'swapTokens' }, { label: 'Query Entity', name: 'queryEntity', type: 'options', options: [ { label: 'Blocks', name: 'https://api.thegraph.com/subgraphs/name/pancakeswap/blocks', description: 'Tracks all blocks on Binance Smart Chain.' }, { label: 'Exchange', name: 'https://bsc.streamingfast.io/subgraphs/name/pancakeswap/exchange-v2', description: 'Tracks all PancakeSwap Exchange data with price, volume, liquidity' }, { label: 'Lottery', name: 'https://api.thegraph.com/subgraphs/name/pancakeswap/lottery', description: 'Tracks all PancakeSwap Lottery with rounds, draws and tickets.' }, { label: 'NFT Market (v1)', name: 'https://api.thegraph.com/subgraphs/name/pancakeswap/nft-market', description: 'Tracks all PancakeSwap NFT Market for ERC-721.' }, { label: 'Pairs', name: 'https://api.thegraph.com/subgraphs/name/pancakeswap/pairs', description: 'Tracks all PancakeSwap Pairs and Tokens.' }, { label: 'Pancake Squad', name: 'https://api.thegraph.com/subgraphs/name/pancakeswap/pancake-squad', description: 'Tracks all Pancake Squad metrics with Owners, Tokens (including metadata), and Transactions.' }, { label: 'Prediction (v1)', name: 'https://api.thegraph.com/subgraphs/name/pancakeswap/prediction', description: 'Tracks all PancakeSwap Prediction (v1) with market, rounds, and bets.' }, { label: 'Prediction (v2)', name: 'https://api.thegraph.com/subgraphs/name/pancakeswap/prediction-v2', description: 'Tracks all PancakeSwap Prediction (v2) with market, rounds, and bets.' }, { label: 'Profile', name: 'https://api.thegraph.com/subgraphs/name/pancakeswap/profile', description: 'Tracks all PancakeSwap Profile with teams, users, points and campaigns.' }, { label: 'SmartChef', name: 'https://api.thegraph.com/subgraphs/name/pancakeswap/smartchef', description: 'Tracks all PancakeSwap SmartChef (a.k.a. Syrup Pools) with tokens and rewards.' }, { label: 'Timelock', name: 'https://api.thegraph.com/subgraphs/name/pancakeswap/timelock', description: 'Tracks all PancakeSwap Timelock queued, executed, and cancelled transactions.' }, { label: 'MasterChef (v2)', name: 'https://api.thegraph.com/subgraphs/name/pancakeswap/masterchef-v2', description: 'Tracks data for MasterChefV2.' } ], default: 'https://api.thegraph.com/subgraphs/name/pancakeswap/pairs', show: { 'actions.operation': ['customQuery'] } } ] as INodeParams[] this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [ { label: 'Binance Smart Chain Mainnet', name: 'bsc', parentGroup: 'Binance Smart Chain' } ], default: 'bsc', show: { 'actions.operation': ['customQuery', 'swapTokens'] } }, { label: 'Network Provider', name: 'networkProvider', type: 'options', options: [...binanceNetworkProviders], default: 'binance', show: { 'actions.operation': ['swapTokens'] } }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'], 'actions.operation': ['swapTokens'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'], 'actions.operation': ['swapTokens'] } } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'QuickNode Endpoints', name: 'quickNodeEndpoints', show: { 'networks.networkProvider': [NETWORK_PROVIDER.QUICKNODE] } } ], show: { 'networks.networkProvider': [NETWORK_PROVIDER.QUICKNODE], 'actions.operation': ['swapTokens'] } } ] as INodeParams[] this.inputParameters = [ { label: 'From Token', name: 'fromToken', type: 'asyncOptions', description: 'Contract address of the token you want to convert FROM.', loadMethod: 'getTokens', show: { 'actions.operation': ['swapTokens'] } }, { label: 'To Token', name: 'toToken', type: 'asyncOptions', description: 'Contract address of the token you want to convert TO.', loadMethod: 'getTokens', show: { 'actions.operation': ['swapTokens'] } }, { label: 'Amount To Swap', name: 'amountToSwap', type: 'number', show: { 'actions.operation': ['swapTokens'] } }, { label: 'Select Wallet', name: 'wallet', type: 'asyncOptions', description: 'Wallet account to swap tokens.', loadFromDbCollections: ['Wallet'], loadMethod: 'getWallets', show: { 'actions.operation': ['swapTokens'] } }, { label: 'Query', name: 'query', type: 'string', rows: 10, show: { 'actions.operation': ['customQuery'] } }, { label: 'Slippage Tolerance (%)', name: 'slippage', type: 'number', default: 0.5, optional: true, description: 'How large of a price movement to tolerate before trade will fail to execute. Default to 0.5%.', show: { 'actions.operation': ['swapTokens'] } }, { label: 'Tx Deadline (mins)', name: 'deadlineMinutes', type: 'number', default: 20, optional: true, description: 'Minutes after which the transaction will fail. Default to 20 mins.', show: { 'actions.operation': ['swapTokens'] } }, { label: 'Disable Multihops', name: 'disableMultihops', type: 'boolean', default: false, optional: true, description: 'Restricts swaps to direct pairs only. Default to false.', show: { 'actions.operation': ['swapTokens'] } } ] as INodeParams[] } loadMethods = { async getTokens(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://tokens.pancakeswap.finance/pancakeswap-extended.json` } const response = await axios(axiosConfig) const responseData = response.data const tokens: IToken[] = responseData.tokens const nativeToken: IToken = nativeTokens[network] // Add native token const data = { label: `${nativeToken.name} (${nativeToken.symbol})`, name: `${nativeToken.address};${nativeToken.symbol};${nativeToken.name};${nativeToken.decimals}` } as INodeOptionsValue returnData.push(data) // Add other tokens for (let i = 0; i < tokens.length; i += 1) { const token = tokens[i] const data = { label: `${token.name} (${token.symbol})`, name: `${token.address};${token.symbol};${token.name};${token.decimals}` } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } }, async getWallets(nodeData: INodeData, dbCollection?: IDbCollection): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) { return returnData } try { if (dbCollection === undefined || !dbCollection || !dbCollection.Wallet) { return returnData } const wallets: IWallet[] = dbCollection.Wallet for (let i = 0; i < wallets.length; i += 1) { const wallet = wallets[i] const data = { label: `${wallet.name} (${wallet.network})`, name: JSON.stringify(wallet), description: wallet.address } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } } } async run(nodeData: INodeData): Promise { const networksData = nodeData.networks const actionsData = nodeData.actions const inputParametersData = nodeData.inputParameters if (networksData === undefined || actionsData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } try { const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, undefined, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') // Get operation const operation = actionsData.operation as string if (operation === 'swapTokens') { // Get fromTokenAddress const fromToken = inputParametersData.fromToken as string const [fromTokenContractAddress, fromTokenSymbol] = fromToken.split(';') // Get toTokenAddress const toToken = inputParametersData.toToken as string const [toTokenContractAddress, toTokenSymbol] = toToken.split(';') // Get wallet instance const walletString = inputParametersData.wallet as string const walletDetails: IWallet = JSON.parse(walletString) const walletCredential = JSON.parse(walletDetails.walletCredential) const wallet = new ethers.Wallet(walletCredential.privateKey as string, provider) // Get amount const amountToSwap = inputParametersData.amountToSwap as string if (fromTokenContractAddress.includes(`_ETH`) && toTokenSymbol === 'WBNB') { const wrapBNBContract = new ethers.Contract(toTokenContractAddress, IWBNB, wallet) const tx = await wrapBNBContract.deposit({ value: ethers.utils.parseUnits(amountToSwap, 18) }) const approveReceipt = await tx.wait() if (approveReceipt.status === 0) throw new Error(`Failed to swap BNB to WBNB`) const returnItem = { transactionHash: tx.hash, transactionReceipt: approveReceipt as any, link: `${networkExplorers[network]}/tx/${tx.hash}` } return returnNodeExecutionData(returnItem) } else if (toTokenContractAddress.includes(`_ETH`) && fromTokenSymbol === 'WBNB') { const wrapBNBContract = new ethers.Contract(fromTokenContractAddress, IWBNB, wallet) const tx = await wrapBNBContract.withdraw(ethers.utils.parseUnits(amountToSwap, 18)) const approveReceipt = await tx.wait() if (approveReceipt.status === 0) throw new Error(`Failed to swap WBNB to BNB`) const returnItem = { transactionHash: tx.hash, transactionReceipt: approveReceipt as any, link: `${networkExplorers[network]}/tx/${tx.hash}` } return returnNodeExecutionData(returnItem) } else { const slippage = inputParametersData.slippage as string const deadlineMinutes = inputParametersData.deadlineMinutes as number const disableMultihops = inputParametersData.disableMultihops as boolean const targets = { v2Override: { routerAddress: '0x10ED43C718714eb63d5aA57B78B54704E256024E', factoryAddress: '0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73', pairAddress: '0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73' } } const customNetworkData = { nameNetwork: 'Binance', multicallContractAddress: '0x65e9a150e06c84003d15ae6a060fc2b1b393342c', nativeCurrency: { name: 'BNB Token', symbol: 'BNB' }, nativeWrappedTokenInfo: { chainId: CHAIN_ID.BINANCE_MAINNET as number, contractAddress: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', decimals: 18, symbol: 'WBNB', name: 'Wrapped BNB' } } const uniswapPair = new UniswapPair({ fromTokenContractAddress, toTokenContractAddress, ethereumAddress: wallet.address, ethereumProvider: provider, settings: new UniswapPairSettings({ slippage: parseFloat(slippage) / 100.0 || 0.0005, deadlineMinutes: deadlineMinutes || 20, disableMultihops: disableMultihops || false, uniswapVersions: [UniswapVersion.v2], cloneUniswapContractDetails: targets, customNetwork: customNetworkData }) }) const uniswapPairFactory = await uniswapPair.createFactory() const trade = await uniswapPairFactory.trade(amountToSwap) if (!trade.fromBalance.hasEnough) { throw new Error('You do not have enough from balance to execute this swap') } // Why we need two transactions: https://github.com/joshstevens19/simple-uniswap-sdk#ethers-example if (trade.approvalTransaction) { const approved = await wallet.sendTransaction(trade.approvalTransaction) await approved.wait() } const tradeTransaction = await wallet.sendTransaction(trade.transaction) const tradeReceipt = await tradeTransaction.wait() trade.destroy() const returnItem = { operation, transactionHash: tradeTransaction.hash, transactionReceipt: tradeReceipt as any, link: `${networkExplorers[network]}/tx/${tradeTransaction.hash}` } return returnNodeExecutionData(returnItem) } } else if (operation === 'customQuery') { let query = inputParametersData.query as string query = query.replace(/\s/g, ' ') const queryEntity = actionsData.queryEntity as string const axiosConfig: AxiosRequestConfig = { method: 'POST' as Method, url: queryEntity, data: { query } } const response = await axios(axiosConfig) const responseData = response.data const returnData: ICommonObject[] = [] if (Array.isArray(responseData)) returnData.push(...responseData) else returnData.push(responseData) return returnNodeExecutionData(returnData) } return returnNodeExecutionData([]) } catch (e) { throw handleErrorMessage(e) } } } module.exports = { nodeClass: PancakeSwap } ================================================ FILE: packages/components/nodes/PancakeSwap/abis/WBNB.json ================================================ [ { "constant": true, "inputs": [], "name": "name", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "guy", "type": "address" }, { "name": "wad", "type": "uint256" } ], "name": "approve", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "totalSupply", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "src", "type": "address" }, { "name": "dst", "type": "address" }, { "name": "wad", "type": "uint256" } ], "name": "transferFrom", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "wad", "type": "uint256" } ], "name": "withdraw", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "decimals", "outputs": [ { "name": "", "type": "uint8" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "", "type": "address" } ], "name": "balanceOf", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "symbol", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "dst", "type": "address" }, { "name": "wad", "type": "uint256" } ], "name": "transfer", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [], "name": "deposit", "outputs": [], "payable": true, "stateMutability": "payable", "type": "function" }, { "constant": true, "inputs": [ { "name": "", "type": "address" }, { "name": "", "type": "address" } ], "name": "allowance", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "payable": true, "stateMutability": "payable", "type": "fallback" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "src", "type": "address" }, { "indexed": true, "name": "guy", "type": "address" }, { "indexed": false, "name": "wad", "type": "uint256" } ], "name": "Approval", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "src", "type": "address" }, { "indexed": true, "name": "dst", "type": "address" }, { "indexed": false, "name": "wad", "type": "uint256" } ], "name": "Transfer", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "dst", "type": "address" }, { "indexed": false, "name": "wad", "type": "uint256" } ], "name": "Deposit", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "src", "type": "address" }, { "indexed": false, "name": "wad", "type": "uint256" } ], "name": "Withdrawal", "type": "event" } ] ================================================ FILE: packages/components/nodes/PancakeSwap/extendedTokens.ts ================================================ import { CHAIN_ID, NETWORK } from '../../src' export interface INativeTokens { [key: string]: IToken } export interface IToken { address: string symbol: string name: string decimals: number chainId: number } export const nativeTokens: INativeTokens = { [NETWORK.BSC]: { address: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c_ETH', symbol: 'BNB', name: 'BNB', decimals: 18, chainId: CHAIN_ID.BINANCE_MAINNET }, [NETWORK.BSC_TESTNET]: { address: '0xae13d989dac2f0debff460ac112a837c89baa7cd_ETH', symbol: 'BNB', name: 'BNB', decimals: 18, chainId: CHAIN_ID.BINANCE_TESTNET } } ================================================ FILE: packages/components/nodes/Pinata/Pinata.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' import axios, { AxiosRequestConfig, AxiosRequestHeaders, Method } from 'axios' import FormData from 'form-data' class Pinata implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Pinata' this.name = 'pinata' this.icon = 'pinata.svg' this.type = 'action' this.category = 'InterPlanetary File System' this.version = 1.0 this.description = 'Pin your content to IPFS with Pinata' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Select Operation', name: 'operation', type: 'options', options: [ { label: 'JSON', name: 'json' }, { label: 'Multiple JSON', name: 'multipleJson', description: 'Wrap each json from an array into a json file (0.json, 1.json..) and store under a directory' }, { label: 'File', name: 'file' }, { label: 'Folder', name: 'folder' }, { label: 'CID (Content Identifier)', name: 'CID' }, { label: 'Raw Data (Base64)', name: 'base64', description: 'Wrap the raw data in base64 format into a file and store under a directory' }, { label: 'Multiple Raw Data (Base64)', name: 'multipleBase64', description: 'Wrap each raw data in base64 format from an array into a file (0.png, 1.pdf..) and store under a directory' }, { label: 'List Pin By CID Jobs', name: 'listPin' }, { label: 'Update Metadata', name: 'updateMetadata' }, { label: 'Remove Files (Unpin)', name: 'unpin' } ], description: 'Content type to be pinned' }, { label: 'JSON Content', name: 'jsonContent', type: 'json', placeholder: '{"var1": "value1"}', description: 'The content of JSON to be pinned.', show: { 'actions.operation': ['json'] } }, { label: 'JSON Content In Array', name: 'jsonContentArray', type: 'json', placeholder: '[ {"var1": "value1"}, {"var2": "value2"} ]', description: 'An array with multiple json objects to be pinned.', show: { 'actions.operation': ['multipleJson'] } }, { label: 'Raw Data Content (Base64)', name: 'base64Content', type: 'string', placeholder: 'data:image/png;base64,', description: 'Raw data content in base64 format to be pinned. Commonly used with image, pdf', show: { 'actions.operation': ['base64'] } }, { label: 'Raw Data Content In Array (Base64)', name: 'base64ContentArray', type: 'json', placeholder: '[ "data:image/png;base64,", "data:application/pdf;base64," ]', description: 'An array with multiple raw data content in base64 format to be pinned. Commonly used with image, pdf', show: { 'actions.operation': ['multipleBase64'] } }, { label: 'File', name: 'fileContent', type: 'file', description: 'The path to a file to be pinned.', show: { 'actions.operation': ['file'] } }, { label: 'Folder', name: 'folderContent', type: 'folder', description: 'The path to a folder to be pinned.', show: { 'actions.operation': ['folder'] } }, { label: 'Hash To Pin (CID)', name: 'hashToPin', type: 'string', description: 'A CID (or content identifier) is a hash generated by the IPFS protocol and representative of a piece of content.', show: { 'actions.operation': ['CID'] }, placeholder: 'Qmc5TDt1jynbZGvLiGnJvLh9RqB4uxVUg4vgMAcPqwpEiy' }, { label: 'IPFS Pin Hash (CID)', name: 'ipfsPinHash', type: 'string', description: 'A CID (or content identifier) is a hash generated by the IPFS protocol and representative of a piece of content.', show: { 'actions.operation': ['updateMetadata'] }, placeholder: 'Qmc5TDt1jynbZGvLiGnJvLh9RqB4uxVUg4vgMAcPqwpEiy' }, { label: 'Pin To Delete (CID)', name: 'pinToDelete', type: 'string', description: 'A CID (or content identifier) is a hash generated by the IPFS protocol and representative of a piece of content.', show: { 'actions.operation': ['unpin'] }, placeholder: 'Qmc5TDt1jynbZGvLiGnJvLh9RqB4uxVUg4vgMAcPqwpEiy' } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Pinata API Key', name: 'pinataApi' } ], default: 'pinataApi' } ] as INodeParams[] this.inputParameters = [ { label: 'CID Version', name: 'cidVersion', type: 'options', options: [ { label: '0 (CIDv0)', name: '0' }, { label: '1 (CIDv1)', name: '1' } ], optional: true, description: 'The CID Version IPFS will use when creating a hash for your content.', show: { 'actions.operation': ['json', 'multipleJson', 'base64', 'multipleBase64', 'file', 'folder', 'CID'] } }, { label: 'Wrap With Directory', name: 'wrapWithDirectory', type: 'boolean', default: false, optional: true, description: 'Wrap your content inside of a directory when adding to IPFS. This allows users to retrieve content via a filename instead of just a hash.', show: { 'actions.operation': ['json', 'base64', 'file', 'folder', 'CID'] } }, { label: 'Metadata Name', name: 'name', type: 'string', default: '', optional: true, description: `A custom name. If you don't provide this value, it will automatically be populated by the original name of the file you've uploaded.`, show: { 'actions.operation': ['json', 'multipleJson', 'base64', 'multipleBase64', 'file', 'folder', 'CID', 'updateMetadata'] } }, { label: 'Metadata KeyValues', name: 'keyvalues', type: 'json', placeholder: '{"var1": "value1"}', optional: true, description: `The key values object allows for users to provide any custom key / value pairs they want for the file being uploaded.`, show: { 'actions.operation': ['json', 'multipleJson', 'base64', 'multipleBase64', 'file', 'folder', 'CID', 'updateMetadata'] } }, { label: 'Host Nodes', name: 'hostNodes', type: 'json', placeholder: '["0xa", "0xb"]', optional: true, description: `Multiaddresses of nodes your content is already stored on. Max 5 nodes.`, show: { 'actions.operation': ['CID'] } }, { label: 'Status', name: 'status', type: 'options', options: [ { label: 'Prechecking', name: 'prechecking', description: 'Pinata is running preliminary validations on your pin request.' }, { label: 'Searching', name: 'searching', description: 'Pinata is actively searching for your content on the IPFS network. This may take some time if your content is isolated.' }, { label: 'Retrieving', name: 'retrieving', description: 'Pinata has located your content and is now in the process of retrieving it.' }, { label: 'Expired', name: 'expired', description: `Pinata wasn't able to find your content after a day of searching the IPFS network. Please make sure your content is hosted on the IPFS network before trying to pin again.` }, { label: 'Over Free Limit', name: 'over_free_limit', description: 'Pinning this object would put you over the free tier limit. Please add a credit card to continue pinning content.' }, { label: 'Over Max Size', name: 'over_max_size', description: `This object is too large of an item to pin. If you're seeing this, please contact us for a more custom solution.` }, { label: 'Invalid Object', name: 'invalid_object', description: `The object you're attempting to pin isn't readable by IPFS nodes. Please contact us if you receive this, as we'd like to better understand what you're attempting to pin.` }, { label: 'Bad Host Node', name: 'bad_host_node', description: 'You provided a host node that was either invalid or unreachable. Please make sure all provided host nodes are online and reachable.' } ], description: `Filter by the status of the job in the pinning queue.`, show: { 'actions.operation': ['listPin'] } } ] } async run(nodeData: INodeData): Promise { const actionData = nodeData.actions const inputParametersData = nodeData.inputParameters const credentials = nodeData.credentials if (actionData === undefined || inputParametersData === undefined || credentials === undefined) { throw new Error('Required data missing') } const operation = actionData.operation as string const apiKey = credentials.apiKey as string const secretKey = credentials.secretKey as string const cidVersion = inputParametersData.cidVersion as string const wrapWithDirectory = inputParametersData.wrapWithDirectory as boolean const name = inputParametersData.name as string const keyvalues = inputParametersData.keyvalues as string const hostNodes = inputParametersData.hostNodes as string const returnData: ICommonObject[] = [] let responseData: any let url = '' const queryParameters: ICommonObject = {} let queryBody: any = {} let method: Method = 'POST' const headers: AxiosRequestHeaders = { 'Content-Type': 'application/json', pinata_api_key: apiKey, pinata_secret_api_key: secretKey } function getOptionsAndMetadata() { // Metadata const pinataMetadataObj: ICommonObject = {} if (name) pinataMetadataObj.name = name if (keyvalues) { const parsedKeyValues = JSON.parse(keyvalues.replace(/\s/g, '')) pinataMetadataObj.keyvalues = parsedKeyValues } if (Object.keys(pinataMetadataObj).length > 0) { queryBody.pinataMetadata = pinataMetadataObj } // Options const pinataOptionsObj: ICommonObject = {} if (cidVersion) pinataOptionsObj.cidVersion = parseInt(cidVersion) if (wrapWithDirectory) pinataOptionsObj.wrapWithDirectory = wrapWithDirectory if (hostNodes) { const hostNodesArray = JSON.parse(hostNodes.replace(/\s/g, '')) if (hostNodesArray.length) pinataOptionsObj.hostNodes = hostNodesArray } if (Object.keys(pinataOptionsObj).length > 0) { queryBody.pinataOptions = pinataOptionsObj } } function getExtension(base64string: string) { switch (base64string.charAt(0)) { case '/': return 'jpg' case 'i': return 'png' case 'R': return 'gif' case 'U': return 'webp' case 'P': return 'svg' default: return 'png' } } try { if (operation === 'json') { // Json Content const jsonContent_str = actionData.jsonContent as string if (wrapWithDirectory) { url = 'https://api.pinata.cloud/pinning/pinFileToIPFS' const jsonContent_base64 = Buffer.from(jsonContent_str).toString('base64') const filename = name ? `${name}.json` : `0.json` const bf = Buffer.from(jsonContent_base64, 'base64') const formData = new FormData() formData.append('file', bf, filename) getOptionsAndMetadata() headers['Content-Type'] = 'multipart/form-data; boundary=' + formData.getBoundary() if (Object.prototype.hasOwnProperty.call(queryBody, 'pinataOptions')) formData.append('pinataOptions', JSON.stringify(queryBody.pinataOptions)) if (Object.prototype.hasOwnProperty.call(queryBody, 'pinataMetadata')) formData.append('pinataMetadata', JSON.stringify(queryBody.pinataMetadata)) queryBody = formData } else { url = 'https://api.pinata.cloud/pinning/pinJSONToIPFS' const parsedInputJson = JSON.parse(jsonContent_str.replace(/\s/g, '')) queryBody.pinataContent = parsedInputJson getOptionsAndMetadata() } } else if (operation === 'multipleJson') { // Json Content Array const jsonContentArray_str = actionData.jsonContentArray as string const jsonContentArray = JSON.parse(jsonContentArray_str) url = 'https://api.pinata.cloud/pinning/pinFileToIPFS' const formData = new FormData() for (let i = 0; i < jsonContentArray.length; i += 1) { const jsonContent_base64 = Buffer.from(JSON.stringify(jsonContentArray[i])).toString('base64') const filename = `${i}.json` const bf = Buffer.from(jsonContent_base64, 'base64') const foldername = name ? name : `MultipleJsonFolder` formData.append('file', bf, { filepath: `${foldername}/${filename}` }) } getOptionsAndMetadata() headers['Content-Type'] = 'multipart/form-data; boundary=' + formData.getBoundary() if (Object.prototype.hasOwnProperty.call(queryBody, 'pinataOptions')) formData.append('pinataOptions', JSON.stringify(queryBody.pinataOptions)) if (Object.prototype.hasOwnProperty.call(queryBody, 'pinataMetadata')) formData.append('pinataMetadata', JSON.stringify(queryBody.pinataMetadata)) queryBody = formData } else if (operation === 'base64') { // Base64 Content const base64Content_str = actionData.base64Content as string //base64Content_str = data:image/png;base64, url = 'https://api.pinata.cloud/pinning/pinFileToIPFS' const splitDataURI = base64Content_str.split(',') const bf = Buffer.from(splitDataURI.pop() || '', 'base64') let ext = 'png' const extension = splitDataURI.pop()?.split(';')[0].split('/')[1] if (extension) ext else ext = getExtension(base64Content_str) const filename = name ? `${name}.${ext}` : `0.${ext}` const formData = new FormData() formData.append('file', bf, filename) getOptionsAndMetadata() headers['Content-Type'] = 'multipart/form-data; boundary=' + formData.getBoundary() if (Object.prototype.hasOwnProperty.call(queryBody, 'pinataOptions')) formData.append('pinataOptions', JSON.stringify(queryBody.pinataOptions)) if (Object.prototype.hasOwnProperty.call(queryBody, 'pinataMetadata')) formData.append('pinataMetadata', JSON.stringify(queryBody.pinataMetadata)) queryBody = formData } else if (operation === 'multipleBase64') { // Base64 Content Array const base64ContentArray_str = actionData.base64ContentArray as string const base64ContentArray = JSON.parse(base64ContentArray_str) url = 'https://api.pinata.cloud/pinning/pinFileToIPFS' const formData = new FormData() for (let i = 0; i < base64ContentArray.length; i += 1) { const splitDataURI = base64ContentArray[i].split(',') const bf = Buffer.from(splitDataURI.pop() || '', 'base64') let ext = 'png' const extension = splitDataURI.pop()?.split(';')[0].split('/')[1] if (extension) ext else ext = getExtension(base64ContentArray[i]) const filename = `${i}.${ext}` const foldername = name ? name : `MultipleRawDataFolder` formData.append('file', bf, { filepath: `${foldername}/${filename}` }) } getOptionsAndMetadata() headers['Content-Type'] = 'multipart/form-data; boundary=' + formData.getBoundary() if (Object.prototype.hasOwnProperty.call(queryBody, 'pinataOptions')) formData.append('pinataOptions', JSON.stringify(queryBody.pinataOptions)) if (Object.prototype.hasOwnProperty.call(queryBody, 'pinataMetadata')) formData.append('pinataMetadata', JSON.stringify(queryBody.pinataMetadata)) queryBody = formData } else if (operation === 'file') { url = 'https://api.pinata.cloud/pinning/pinFileToIPFS' const fileBase64 = actionData.fileContent as string const splitDataURI = fileBase64.split(',') const filename = (splitDataURI.pop() || 'filename:').split(':')[1] const bf = Buffer.from(splitDataURI.pop() || '', 'base64') const formData = new FormData() formData.append('file', bf, filename) getOptionsAndMetadata() headers['Content-Type'] = 'multipart/form-data; boundary=' + formData.getBoundary() if (Object.prototype.hasOwnProperty.call(queryBody, 'pinataOptions')) formData.append('pinataOptions', JSON.stringify(queryBody.pinataOptions)) if (Object.prototype.hasOwnProperty.call(queryBody, 'pinataMetadata')) formData.append('pinataMetadata', JSON.stringify(queryBody.pinataMetadata)) queryBody = formData } else if (operation === 'folder') { url = 'https://api.pinata.cloud/pinning/pinFileToIPFS' const folderContent = actionData.folderContent as string const base64Array = JSON.parse(folderContent.replace(/\s/g, '')) const formData = new FormData() for (let i = 0; i < base64Array.length; i += 1) { const fileBase64 = base64Array[i] const splitDataURI = fileBase64.split(',') const filepath = (splitDataURI.pop() || 'filepath:').split(':')[1] const bf = Buffer.from(splitDataURI.pop() || '', 'base64') formData.append('file', bf, { filepath }) } getOptionsAndMetadata() headers['Content-Type'] = 'multipart/form-data; boundary=' + formData.getBoundary() if (Object.prototype.hasOwnProperty.call(queryBody, 'pinataOptions')) formData.append('pinataOptions', JSON.stringify(queryBody.pinataOptions)) if (Object.prototype.hasOwnProperty.call(queryBody, 'pinataMetadata')) formData.append('pinataMetadata', JSON.stringify(queryBody.pinataMetadata)) queryBody = formData } else if (operation === 'CID') { url = 'https://api.pinata.cloud/pinning/pinByHash' const hashToPin = actionData.hashToPin as string queryBody.hashToPin = hashToPin getOptionsAndMetadata() } else if (operation === 'listPin') { url = 'https://api.pinata.cloud/pinning/pinJobs' method = 'GET' const status = inputParametersData.status as string queryParameters.status = status queryParameters.sort = 'ASC' } else if (operation === 'updateMetadata') { url = 'https://api.pinata.cloud/pinning/hashMetadata' method = 'PUT' const ipfsPinHash = actionData.ipfsPinHash as string queryBody.ipfsPinHash = ipfsPinHash if (name) queryBody.name = name if (keyvalues) { const parsedKeyValues = JSON.parse(keyvalues.replace(/\s/g, '')) queryBody.keyvalues = parsedKeyValues } } else if (operation === 'unpin') { const pinToDelete = actionData.pinToDelete as string url = `https://api.pinata.cloud/pinning/unpin/${pinToDelete}` method = 'DELETE' } const axiosConfig: AxiosRequestConfig = { method, url, headers } if (Object.keys(queryParameters).length > 0) { axiosConfig.params = queryParameters axiosConfig.paramsSerializer = (params) => serializeQueryParams(params) } if (Object.keys(queryBody).length > 0) { axiosConfig.data = queryBody } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) returnData.push(...responseData) else returnData.push(responseData) return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: Pinata } ================================================ FILE: packages/components/nodes/PolygonScan/PolygonScan.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' import { SORT_BY, OPERATIONS, GET_MATIC_BALANCE, GET_HISTORICAL_MATIC_BALANCE, GET_NORMAL_TRANSACTIONS, GET_INTERNAL_TRANSACTIONS, GET_INTERNAL_TRANSACTIONS_BY_HASH, GET_INTERNAL_TRANSACTIONS_BY_BLOCK, GET_BLOCKS_VALIDATED, GET_ABI, GET_CONTRACT_SOURCE_CODE, CHECK_TRANSACTION_RECEIPT_STATUS, GET_ERC20_TOKEN_SUPPLY, GET_ERC20_TOKEN_BALANCE, GET_HISTORICAL_ERC20_TOKEN_SUPPLY, GET_HISTORICAL_ERC20_TOKEN_BALANCE, GET_TOKEN_INFO, GET_MATIC_PRICE, GET_HISTORICAL_MATIC_PRICE } from './constants' class PolygonScan implements INode { // properties label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number // parameter actions: INodeParams[] credentials?: INodeParams[] networks?: INodeParams[] inputParameters?: INodeParams[] constructor() { // properties this.label = 'PolygonScan' this.name = 'polygonscan' this.icon = 'polygonscan.png' this.type = 'action' this.category = 'Block Explorer' this.version = 1.0 this.description = 'PolygonScan Public API' this.incoming = 1 this.outgoing = 1 // parameter this.actions = [ { label: 'API', name: 'api', type: 'options', options: [ { label: 'Get MATIC Balance', name: GET_MATIC_BALANCE.name, description: 'Returns the MATIC balance of a given address. The result is returned in wei.' }, { label: 'Get Historical MATIC Balance [PRO]', name: GET_HISTORICAL_MATIC_BALANCE.name, description: 'Returns the historical MATIC balance of an address at a certain block height. The result is returned in wei' }, { label: 'Get transactions', name: GET_NORMAL_TRANSACTIONS.name, description: 'Returns the list of transactions performed by an address, with optional pagination.' }, { label: 'Get internal transactions', name: GET_INTERNAL_TRANSACTIONS.name, description: 'Returns the list of internal transactions performed by an address, with optional pagination.' }, { label: 'Get internal transactions by hash', name: GET_INTERNAL_TRANSACTIONS_BY_HASH.name, description: 'Returns the list of internal transactions performed within a transaction.' }, { label: 'Get internal transactions by block', name: GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name, description: 'Returns the list of internal transactions performed within a block range, with optional pagination.' }, { label: 'Get list of Blocks Validated by Address', name: GET_BLOCKS_VALIDATED.name, description: 'Returns the list of blocks validated by an address.' }, { label: 'Get Contract ABI', name: GET_ABI.name, description: 'Returns the contract Application Binary Interface ( ABI ) of a verified smart contract.' }, { label: 'Get Contract Source Code', name: GET_CONTRACT_SOURCE_CODE.name, description: 'Returns the Solidity source code of a verified smart contract.' }, { label: 'Check Transaction Receipt Status', name: CHECK_TRANSACTION_RECEIPT_STATUS.name, description: 'Returns the status code of a transaction execution.' }, { label: 'Get ERC20 Token Supply', name: GET_ERC20_TOKEN_SUPPLY.name, description: `Returns the total supply of a ERC-20 token. The result is returned in the token's smallest decimal representation. Eg. a token with a balance of 215.241526476136819398 and 18 decimal places will be returned as 215241526476136819398` }, { label: 'Get ERC20 Token Balance', name: GET_ERC20_TOKEN_BALANCE.name, description: `Returns the current balance of a ERC-20 token of an address. The result is returned in the token's smallest decimal representation. Eg. a token with a balance of 215.241526476136819398 and 18 decimal places will be returned as 215241526476136819398` }, { label: 'Get Historical ERC-20 Token TotalSupply by ContractAddress & BlockNo [PRO]', name: GET_HISTORICAL_ERC20_TOKEN_SUPPLY.name, description: `Returns the historical amount of a ERC-20 token in circulation at a certain block height. The result is returned in the token's smallest decimal representation. Eg. a token with a balance of 215.241526476136819398 and 18 decimal places will be returned as 215241526476136819398` }, { label: 'Get Historical ERC-20 Token Account Balance by ContractAddress & BlockNo [PRO]', name: GET_HISTORICAL_ERC20_TOKEN_BALANCE.name, description: `Returns the balance of a ERC-20 token of an address at a certain block height. The result is returned in the token's smallest decimal representation. Eg. a token with a balance of 215.241526476136819398 and 18 decimal places will be returned as 215241526476136819398` }, { label: 'Get Token Info [PRO]', name: GET_TOKEN_INFO.name, description: 'Returns project information and social media links of an ERC-20/ERC-721 token.' }, { label: 'Get MATIC Price', name: GET_MATIC_PRICE.name, description: 'Returns the latest price of 1 MATIC.' }, { label: 'Get Historical MATIC Price [PRO]', name: GET_HISTORICAL_MATIC_PRICE.name, description: 'Returns the historical price of 1 MATIC.' } ], default: GET_MATIC_BALANCE.name } ] as INodeParams[] this.networks = [ { label: 'Network', name: 'network', type: 'options', description: 'Network to execute API: Test or Real', options: [ { label: 'Polygon Testnet', name: 'testnet' }, { label: 'Polygon Mainnet', name: 'mainnet' } ], default: 'testnet' } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'PolygonScan API Key', name: 'polygonscanApi' } ], default: 'polygonscanApi' } ] as INodeParams[] this.inputParameters = [ { label: 'Polygon Address', name: 'address', type: 'string', show: { 'actions.api': [ GET_MATIC_BALANCE.name, GET_HISTORICAL_MATIC_BALANCE.name, GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_BLOCKS_VALIDATED.name, GET_ABI.name, GET_CONTRACT_SOURCE_CODE.name, GET_ERC20_TOKEN_BALANCE.name, GET_HISTORICAL_ERC20_TOKEN_BALANCE.name ] } }, { label: 'Block Number', name: 'blockno', type: 'number', description: 'the block number to check balance for eg. 2000000', show: { 'actions.api': [ GET_HISTORICAL_MATIC_BALANCE.name, GET_HISTORICAL_ERC20_TOKEN_SUPPLY.name, GET_HISTORICAL_ERC20_TOKEN_BALANCE.name ] } }, { label: 'Start Block', name: 'startBlock', type: 'number', optional: true, description: 'the block number to start searching for transactions', show: { 'actions.api': [GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name] }, default: 0 }, { label: 'End Block', name: 'endBlock', type: 'number', optional: true, description: 'the block number to stop searching for transactions', show: { 'actions.api': [GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name] } }, { label: 'Page', name: 'page', type: 'number', optional: true, description: 'the page number, if pagination is enabled', show: { 'actions.api': [ GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name, GET_BLOCKS_VALIDATED.name ] }, default: 1 }, { label: 'Offset', name: 'offset', type: 'number', optional: true, description: 'the number of transactions displayed per page', show: { 'actions.api': [ GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name, GET_BLOCKS_VALIDATED.name ] }, default: 10 }, { label: 'Sort By', name: 'sortBy', type: 'options', optional: true, options: SORT_BY, show: { 'actions.api': [GET_NORMAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS.name, GET_INTERNAL_TRANSACTIONS_BY_BLOCK.name] }, default: 'desc' }, { label: 'Transaction Hash', name: 'txhash', type: 'string', description: 'the string representing the transaction hash to check for internal transactions', show: { 'actions.api': [GET_INTERNAL_TRANSACTIONS_BY_HASH.name, CHECK_TRANSACTION_RECEIPT_STATUS.name] } }, { label: 'Block Type', name: 'blockType', type: 'options', options: [ { label: 'blocks', name: 'blocks' } ], default: 'blocks', show: { 'actions.api': [GET_BLOCKS_VALIDATED.name] } }, { label: 'Contract Address', name: 'contractAddress', type: 'string', description: 'the contract address of the ERC-20 token', show: { 'actions.api': [ GET_ERC20_TOKEN_SUPPLY.name, GET_ERC20_TOKEN_BALANCE.name, GET_HISTORICAL_ERC20_TOKEN_SUPPLY.name, GET_HISTORICAL_ERC20_TOKEN_BALANCE.name, GET_TOKEN_INFO.name ] } }, { label: 'Tag', name: 'tag', type: 'options', options: [{ label: 'latest', name: 'latest' }], default: 'latest', show: { 'actions.api': [GET_ERC20_TOKEN_BALANCE.name] } }, { label: 'Start Time', name: 'startTime', type: 'date', optional: true, show: { 'actions.api': [GET_HISTORICAL_MATIC_PRICE.name] } }, { label: 'End Time', name: 'endTime', type: 'date', optional: true, show: { 'actions.api': [GET_HISTORICAL_MATIC_PRICE.name] } } ] } getNetwork(network: string): string { switch (network) { case 'mainnet': return 'https://api.polygonscan.com/api' case 'testnet': default: return 'https://api-testnet.polygonscan.com/api' } } getBaseParams(api: string) { const operation = OPERATIONS.filter(({ name }) => name === api)[0] return { module: operation.module, action: operation.action } } getISODate(date: Date) { return date.toISOString().split('T')[0] } async run(nodeData: INodeData): Promise { const { actions, networks, inputParameters, credentials } = nodeData if (actions === undefined || inputParameters === undefined || credentials === undefined || networks === undefined) { throw new Error('Required data missing') } const api = actions.api as string const network = networks.network as string const apiKey = credentials.apiKey as string const address = inputParameters.address as string const startblock = inputParameters.startBlock as number const endblock = inputParameters.endBlock as number const page = inputParameters.page as number const offset = inputParameters.offset as number const sort = inputParameters.sortBy as string const txhash = inputParameters.txhash as string const blocktype = inputParameters.blockType as string const contractaddress = inputParameters.contractAddress as string const tag = inputParameters.tag as string const startTime = inputParameters.startTime as string const endTime = inputParameters.endTime as string const startdate = startTime ? this.getISODate(new Date(startTime)) : undefined const enddate = endTime ? this.getISODate(new Date(endTime)) : undefined const url = this.getNetwork(network) const { module, action } = this.getBaseParams(api) const queryParameters = { module, action, address, apiKey, startblock, endblock, page, offset, sort, txhash, blocktype, contractaddress, tag, startdate, enddate } const returnData: ICommonObject[] = [] let responseData: any try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url, params: queryParameters, paramsSerializer: (params) => serializeQueryParams(params), headers: { 'Content-Type': 'application/json' } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: PolygonScan } ================================================ FILE: packages/components/nodes/PolygonScan/constants.ts ================================================ // Account export const GET_MATIC_BALANCE = { name: 'getMaticBalance', module: 'account', action: 'balance' } export const GET_HISTORICAL_MATIC_BALANCE = { name: 'getHistoricalMaticBalance', module: 'account', action: 'balancehistory' } export const GET_NORMAL_TRANSACTIONS = { name: 'getTransactions', module: 'account', action: 'txlist' } export const GET_INTERNAL_TRANSACTIONS = { name: 'getInternalTransactions', module: 'account', action: 'txlistinternal' } export const GET_INTERNAL_TRANSACTIONS_BY_HASH = { name: 'getInternalTransactionsByHash', module: 'account', action: 'txlistinternal' } export const GET_INTERNAL_TRANSACTIONS_BY_BLOCK = { name: 'getInternalTransactionsByBlock', module: 'account', action: 'txlistinternal' } export const GET_BLOCKS_VALIDATED = { name: 'getBlocksValidated', module: 'account', action: 'getminedblocks' } // Contracts export const GET_ABI = { name: 'getAbi', module: 'contract', action: 'getabi' } export const GET_CONTRACT_SOURCE_CODE = { name: 'getContractSourceCode', module: 'contract', action: 'getsourcecode' } // Transactions export const CHECK_TRANSACTION_RECEIPT_STATUS = { name: 'getTransactionReceiptStatus', module: 'transaction', action: 'gettxreceiptstatus' } // Tokens export const GET_ERC20_TOKEN_SUPPLY = { name: 'getErc20TokenSupply', module: 'stats', action: 'tokensupply' } export const GET_ERC20_TOKEN_BALANCE = { name: 'getErc20TokenBalance', module: 'account', action: 'tokenbalance' } export const GET_HISTORICAL_ERC20_TOKEN_SUPPLY = { name: 'getHistoricalErc20TokenSupply', module: 'stats', action: 'tokensupplyhistory' } export const GET_HISTORICAL_ERC20_TOKEN_BALANCE = { name: 'getHistoricalErc20TokenBalance', module: 'account', action: 'tokenbalancehistory' } export const GET_TOKEN_INFO = { name: 'getTokenInfo', module: 'token', action: 'tokeninfo' } // Stats export const GET_MATIC_PRICE = { name: 'getMaticPrice', module: 'stats', action: 'maticprice' } export const GET_HISTORICAL_MATIC_PRICE = { name: 'getHistoricalMaticPrice', module: 'stats', action: 'ethdailyprice' } export const OPERATIONS = [ GET_MATIC_BALANCE, GET_HISTORICAL_MATIC_BALANCE, GET_NORMAL_TRANSACTIONS, GET_INTERNAL_TRANSACTIONS, GET_INTERNAL_TRANSACTIONS_BY_HASH, GET_INTERNAL_TRANSACTIONS_BY_BLOCK, GET_BLOCKS_VALIDATED, GET_ABI, GET_CONTRACT_SOURCE_CODE, CHECK_TRANSACTION_RECEIPT_STATUS, GET_ERC20_TOKEN_SUPPLY, GET_ERC20_TOKEN_BALANCE, GET_HISTORICAL_ERC20_TOKEN_SUPPLY, GET_HISTORICAL_ERC20_TOKEN_BALANCE, GET_TOKEN_INFO, GET_MATIC_PRICE, GET_HISTORICAL_MATIC_PRICE ] as const export const SORT_BY = [ { label: 'Desc', name: 'desc' }, { label: 'Asc', name: 'asc' } ] ================================================ FILE: packages/components/nodes/QuickNode/QuickNode.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import { NETWORK } from '../../src/ChainNetwork' import { ethOperations, IETHOperation, operationCategoryMapping, qnSupportedNetworks } from '../../src/ETHOperations' import axios, { AxiosRequestConfig, AxiosRequestHeaders, Method } from 'axios' import { arbTraceOperationsNetworks, avaxOperations, avaxOperationsNetworks, debugOperations, debugOperationsNetworks, fantomOperations, fantomOperationsNetworks, nftOperations, nftOperationsNetworks, platformOperations, tokenOperations, tokenOperationsNetworks, traceOperations, traceOperationsNetworks } from './extendedOperation' import { QuickNodeSupportedNetworks } from './supportedNetwork' import { solanaOperations, solanaOperationsNetworks } from './solanaOperation' class QuickNode implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number credentials?: INodeParams[] networks?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'QuickNode' this.name = 'quickNode' this.icon = 'quicknode.svg' this.type = 'action' this.category = 'Network Provider' this.version = 1.0 this.description = 'Perform QuickNode onchain operations' this.incoming = 1 this.outgoing = 1 this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...QuickNodeSupportedNetworks] } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'QuickNode Endpoints', name: 'quickNodeEndpoints' } ], default: 'quickNodeEndpoints' } ] as INodeParams[] this.inputParameters = [ { label: 'API', name: 'api', type: 'options', options: [ { label: 'EVM Chain API', name: 'chainAPI', description: 'API for fetching standard onchain data using QuickNode supported calls', show: { 'networks.network': qnSupportedNetworks } }, { label: 'NFT API', name: 'nftAPI', description: 'API for fetching cross-chain NFT data using QuickNode supported calls', show: { 'networks.network': [...nftOperationsNetworks, ...solanaOperationsNetworks] } }, { label: 'Token API', name: 'tokenAPI', description: 'API to look up ERC-20 tokens by wallet, token transfers, and token details instantly', show: { 'networks.network': tokenOperationsNetworks } }, { label: 'Debug API', name: 'debugAPI', description: 'API for fetching debug traces using QuickNode supported calls (Trace Mode required)', show: { 'networks.network': debugOperationsNetworks } }, { label: 'Trace API', name: 'traceAPI', description: 'API for fetching traces using QuickNode supported calls (Trace Mode required and supported only on OpenEthereum & Erigon)', show: { 'networks.network': [...traceOperationsNetworks, ...arbTraceOperationsNetworks] } }, { label: 'Avalanche API', name: 'avaxAPI', description: 'API for fetching Avalanche on-chain data using QuickNode supported calls', show: { 'networks.network': avaxOperationsNetworks } }, { label: 'Platform API', name: 'platformAPI', description: 'API for fetching Avalanche Platform data using QuickNode supported calls', show: { 'networks.network': avaxOperationsNetworks } }, { label: 'Fantom API', name: 'fantomAPI', description: 'API for fetching Fantom on-chain data using QuickNode supported calls', show: { 'networks.network': fantomOperationsNetworks } }, { label: 'Solana API', name: 'solanaAPI', description: 'API for fetching Solana on-chain data using QuickNode supported calls', show: { 'networks.network': solanaOperationsNetworks } } ], default: 'chainAPI' }, { label: 'Chain Category', name: 'chainCategory', type: 'options', options: [ { label: 'Retrieving Blocks', name: 'retrievingBlocks', description: 'Retrieve onchain blocks data' }, { label: 'EVM/Smart Contract Execution', name: 'evmExecution', description: 'Execute or submit transaction onto blockchain' }, { label: 'Reading Transactions', name: 'readingTransactions', description: 'Read onchain transactions data' }, { label: 'Account Information', name: 'accountInformation', description: 'Retrieve onchain account information' }, { label: 'Event Logs', name: 'eventLogs', description: 'Fetch onchain logs' }, { label: 'Chain Information', name: 'chainInformation', description: 'Get general selected blockchain information' }, { label: 'Retrieving Uncles', name: 'retrievingUncles', description: 'Retrieve onchain uncles blocks data' }, { label: 'Filters', name: 'filters', description: 'Get block filters and logs, or create new filter' } ], show: { 'inputParameters.api': ['chainAPI'] } }, { label: 'Chain Category', name: 'chainCategory', type: 'options', options: [ { label: 'Reading & Writing Transactions', name: 'readWriteTransactions', description: 'Read and Write transactins onto Solana chain' }, { label: 'Getting Blocks', name: 'gettingBlocks', description: 'Get Solana blocks data' }, { label: 'Account Information', name: 'accountInformation', description: 'Retrieve Solana onchain account information' }, { label: 'Network Information', name: 'networkInformation', description: 'Get Solana network onchain information' }, { label: 'Slot Information', name: 'slotInformation', description: 'Fetch Solana slot information' }, { label: 'Node Information', name: 'nodeInformation', description: 'Retrieve Solana node onchain information' }, { label: 'Token Information', name: 'tokenInformation', description: 'Fetch Solana onchain token information' }, { label: 'Network Inflation', name: 'networkInflation', description: 'Retrieve Solana network inflation onchain data' } ], show: { 'inputParameters.api': ['solanaAPI'] } }, { label: 'Operation', name: 'operation', type: 'asyncOptions', loadMethod: 'getOperations' }, { label: 'Parameters', name: 'parameters', type: 'json', placeholder: `[ "param1", "param2" ]`, optional: true, description: 'Operation parameters in array. Ex: ["param1", "param2"]', show: { 'inputParameters.api': ['chainAPI', 'tokenAPI', 'traceAPI', 'debugAPI', 'fantomAPI'] } }, { label: 'Parameters', name: 'parameters', type: 'json', placeholder: `{ "param1": "value1" }`, optional: true, description: 'Operation parameters in JSON. Ex: {"param1": "value1"}', show: { 'inputParameters.api': ['nftAPI', 'avaxAPI', 'platformAPI'] }, hide: { 'inputParameters.operation': ['qn_verifyNFTsOwner', 'qn_fetchNFTsByCreator', 'requestAirdrop'] } }, { label: 'Parameters', name: 'parameters', type: 'json', placeholder: `[ "param1", "param2" ]`, optional: true, description: 'Operation parameters in array. Ex: ["param1", "param2"]', show: { 'inputParameters.operation': ['qn_verifyNFTsOwner', 'qn_fetchNFTsByCreator', 'requestAirdrop'] } } ] as INodeParams[] } loadMethods = { async getOperations(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const inputParametersData = nodeData.inputParameters const networksData = nodeData.networks if (networksData === undefined || inputParametersData === undefined) { return returnData } const api = inputParametersData.api as string const chainCategory = inputParametersData.chainCategory as string const network = networksData.network as NETWORK const operations = getSelectedOperations(api).filter( (op: IETHOperation) => Object.prototype.hasOwnProperty.call(op.providerNetworks, 'quicknode') && op.providerNetworks['quicknode'].includes(network) ) if (api === 'chainAPI' && !chainCategory) return returnData if (api === 'solanaAPI' && !chainCategory) return returnData let filteredOperations: IETHOperation[] = operations if (api === 'chainAPI' || api === 'solanaAPI') filteredOperations = operations.filter((op: IETHOperation) => op.parentGroup === operationCategoryMapping[chainCategory]) for (const op of filteredOperations) { returnData.push({ label: op.name, name: op.value, parentGroup: op.parentGroup, description: op.description, inputParameters: op.inputParameters, exampleParameters: op.exampleParameters, exampleResponse: op.exampleResponse }) } return returnData } } async run(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters const credentials = nodeData.credentials if (inputParametersData === undefined) { throw new Error('Required data missing') } if (credentials === undefined) { throw new Error('Missing credentials') } // GET api const api = inputParametersData.api as string // GET credentials const httpProvider = credentials.httpProvider as string // GET operation const operation = inputParametersData.operation as string let uri = httpProvider let responseData: any // tslint:disable-line: no-any let bodyParameters: any[] = [] // tslint:disable-line: no-any const returnData: ICommonObject[] = [] const headers: AxiosRequestHeaders = { 'Content-Type': 'application/json' } const parameters = inputParametersData.parameters as string if (parameters) { try { bodyParameters = JSON.parse(parameters.replace(/\s/g, '')) } catch (error) { throw handleErrorMessage(error) } } try { let totalOperations: IETHOperation[] = [] totalOperations = getSelectedOperations(api) const result = totalOperations.find((obj) => { return obj.value === operation }) if (result === undefined) throw new Error('Invalid Operation') const requestBody = JSON.parse(JSON.stringify(result.body)) const bodyParams = requestBody.params requestBody.params = Array.isArray(bodyParameters) ? bodyParameters.concat(bodyParams) : bodyParameters if (result.overrideUrl) { uri = result.overrideUrl.replace('http://sample-endpoint-name.network.quiknode.pro/token-goes-here/', httpProvider) } if (api === 'nftAPI') { headers['x-qn-api-version'] = 1 } const axiosConfig: AxiosRequestConfig = { method: result.method as Method, url: uri, data: requestBody, headers } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) returnData.push(...responseData) else returnData.push(responseData) return returnNodeExecutionData(returnData) } } const getSelectedOperations = (api: string) => { switch (api) { case 'chainAPI': return ethOperations case 'nftAPI': return nftOperations case 'debugAPI': return debugOperations case 'traceAPI': return traceOperations case 'tokenAPI': return tokenOperations case 'avaxAPI': return avaxOperations case 'fantomAPI': return fantomOperations case 'platformAPI': return platformOperations case 'solanaAPI': return solanaOperations default: return ethOperations } } module.exports = { nodeClass: QuickNode } ================================================ FILE: packages/components/nodes/QuickNode/QuickNodeTrigger.ts ================================================ import { INode, INodeData, INodeOptionsValue, INodeParams, IProviders, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import EventEmitter from 'events' import { NETWORK } from '../../src/ChainNetwork' import { QuickNodeSupportedNetworks } from './supportedNetwork' import { subscribeOperations, unsubscribeOperations } from './subscribeOperation' import { IETHOperation } from '../../src/ETHOperations' import WebSocket from 'ws' class QuickNodeTrigger extends EventEmitter implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] providers: IProviders constructor() { super() this.label = 'QuickNode Trigger' this.name = 'quickNodeTrigger' this.icon = 'quicknode.svg' this.type = 'trigger' this.category = 'Network Provider' this.version = 1.0 this.description = 'Start workflow whenever subscribed event happened' this.incoming = 0 this.outgoing = 1 this.providers = {} this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...QuickNodeSupportedNetworks] } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'QuickNode Endpoints', name: 'quickNodeEndpoints' } ], default: 'quickNodeEndpoints' } ] as INodeParams[] this.inputParameters = [ { label: 'Subscribe Operation', name: 'subscribeOperation', type: 'asyncOptions', loadMethod: 'getSubscribeOperations' }, { label: 'Parameters', name: 'parameters', type: 'json', placeholder: `[ "param1", "param2" ]`, optional: true, description: 'Operation parameters in array. Ex: ["param1", "param2"]' }, { label: 'Unsubscribe Operation', name: 'unsubscribeOperation', type: 'asyncOptions', loadMethod: 'getUnsubscribeOperations' } ] as INodeParams[] } loadMethods = { async getSubscribeOperations(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) { return returnData } const network = networksData.network as NETWORK const totalOperations = subscribeOperations const filteredOperations = totalOperations.filter( (op: IETHOperation) => Object.prototype.hasOwnProperty.call(op.providerNetworks, 'quicknode') && op.providerNetworks['quicknode'].includes(network) ) for (const op of filteredOperations) { returnData.push({ label: op.name, name: op.value, parentGroup: op.parentGroup, description: op.description, inputParameters: op.inputParameters, exampleParameters: op.exampleParameters, exampleResponse: op.exampleResponse }) } return returnData }, async getUnsubscribeOperations(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) { return returnData } const network = networksData.network as NETWORK const totalOperations = unsubscribeOperations const filteredOperations = totalOperations.filter( (op: IETHOperation) => Object.prototype.hasOwnProperty.call(op.providerNetworks, 'quicknode') && op.providerNetworks['quicknode'].includes(network) ) for (const op of filteredOperations) { returnData.push({ label: op.name, name: op.value, parentGroup: op.parentGroup, description: op.description, inputParameters: op.inputParameters, exampleParameters: op.exampleParameters, exampleResponse: op.exampleResponse }) } return returnData } } async runTrigger(nodeData: INodeData): Promise { const networksData = nodeData.networks const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (networksData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } if (credentials === undefined) { throw new Error('Missing credentials') } // GET credentials const wssProvider = credentials.wssProvider as string // GET subscribeOperation const subscribeOperation = inputParametersData.subscribeOperation as string // GET parameters let bodyParameters: any const parameters = inputParametersData.parameters as string if (parameters) { try { bodyParameters = JSON.parse(parameters.replace(/\s/g, '')) } catch (error) { throw handleErrorMessage(error) } } const emitEventKey = nodeData.emitEventKey as string const result = subscribeOperations.find((obj) => { return obj.value === subscribeOperation }) if (result === undefined) throw new Error('Invalid Operation') const requestBody = result.body requestBody.params = bodyParameters const ws = new WebSocket(wssProvider) ws.on('open', function open() { ws.send(JSON.stringify(requestBody)) }) let subscriptionID = '' ws.on('message', (data) => { const messageData = JSON.parse(data as any) if (messageData.method) { this.emit(emitEventKey, returnNodeExecutionData(messageData)) } else { subscriptionID = messageData.result this.providers[emitEventKey] = { provider: ws, filter: subscriptionID } } }) } async removeTrigger(nodeData: INodeData): Promise { const emitEventKey = nodeData.emitEventKey as string const inputParametersData = nodeData.inputParameters if (inputParametersData === undefined) { throw new Error('Required data missing') } if (Object.prototype.hasOwnProperty.call(this.providers, emitEventKey)) { const provider: WebSocket = this.providers[emitEventKey].provider const subscriptionID = this.providers[emitEventKey].filter const result = unsubscribeOperations.find((obj) => { return obj.value === (inputParametersData.unsubscribeOperation as string) }) if (result === undefined) throw new Error('Invalid Operation') const requestBody = result.body requestBody.params = [subscriptionID] provider.send(JSON.stringify(requestBody)) provider.close(1000) this.removeAllListeners(emitEventKey) } } } module.exports = { nodeClass: QuickNodeTrigger } ================================================ FILE: packages/components/nodes/QuickNode/extendedOperation.ts ================================================ import { NETWORK, NETWORK_PROVIDER } from '../../src/ChainNetwork' import { IETHOperation, infuraSupportedNetworks } from '../../src/ETHOperations' export const debugOperationsNetworks = [...infuraSupportedNetworks, NETWORK.ARBITRUM_NOVA, NETWORK.BSC, NETWORK.BSC_TESTNET, NETWORK.CELO] export const debugOperations = [ { name: 'Debug Trace Block By Hash (debug_traceBlockByHash)', value: 'debug_traceBlockByHash', parentGroup: 'Debug Traces', description: 'Replay the block that is already present in the database (Trace Mode required)', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: debugOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'debug_traceBlockByHash', params: [{ tracer: 'callTracer' }], id: 1 }, inputParameters: `
    • HASH - Hash of the block to be traced.
    `, exampleParameters: `[ "0x97b49e43632ac70c46b4003434058b18db0ad809617bd29f3448d46ca9085576" ]` }, { name: 'Debug Trace Block By Number (debug_traceBlockByNumber)', value: 'debug_traceBlockByNumber', parentGroup: 'Debug Traces', description: 'Replay the block that is already present in the database (Trace Mode required)', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: debugOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'debug_traceBlockByNumber', params: [{ tracer: 'callTracer' }], id: 1 }, inputParameters: `
    • QUANTITY|TAG - integer block number, or the string "latest", "earliest" or "pending", OR the blockHash (in accordance with EIP-1898)
    `, exampleParameters: `[ "0xccde12" ]` }, { name: 'Debug Trace Call (debug_traceCall)', value: 'debug_traceCall', parentGroup: 'Debug Traces', description: `Let's you run eth_call on top of a block (Trace Mode required)`, providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: debugOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'debug_traceCall', params: [{ tracer: 'callTracer' }], id: 1 }, inputParameters: `
    • Object - The transaction call object
      • from: DATA, 20 Bytes - (optional) The address the transaction is sent from.
      • to: DATA, 20 Bytes - The address the transaction is directed to.
      • gas: QUANTITY - (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions.
      • gasPrice: QUANTITY - (optional) Integer of the gasPrice used for each paid gas.
      • value: QUANTITY - (optional) Integer of the value sent with this transaction.
      • data: DATA - (optional) Hash of the method signature and encoded parameters.
    • QUANTITY|TAG - integer block number, or the string "latest", "earliest" or "pending", OR the blockHash (in accordance with EIP-1898)
    `, exampleParameters: `[ { "from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", "gas": "0x76c0", "gasPrice": "0x9184e72a000", "value": "0x9184e72a", "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" }, "latest" ]` }, { name: 'Debug Trace Transaction (debug_traceTransaction)', value: 'debug_traceTransaction', parentGroup: 'Debug Traces', description: 'Returns all traces of given transaction (Trace Mode required)', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: debugOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'debug_traceBlockByHash', params: [{ tracer: 'callTracer' }], id: 1 }, inputParameters: `
    • HASH - The hash of a transaction.
    `, exampleParameters: `[ "0x97b49e43632ac70c46b4003434058b18db0ad809617bd29f3448d46ca9085576" ]` } ] as IETHOperation[] export const arbTraceOperationsNetworks = [NETWORK.ARBITRUM, NETWORK.ARBITRUM_GOERLI] export const arbTraceOperations = [ { name: 'Arbtrace Block (arbtrace_block )', value: 'arbtrace_block', parentGroup: 'Traces', description: 'Returns traces created at given block (Trace Mode required, and supported only on OpenEthereum & Erigon).', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: arbTraceOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'arbtrace_block ', params: [], id: 1 }, inputParameters: `
    • QUANTITY|TAG - integer block number, or the string "latest", "earliest" or "pending", OR the blockHash (in accordance with EIP-1898)
    `, exampleParameters: `[ "0xccb93d" ]` }, { name: 'Arbtrace Call (arbtrace_call)', value: 'arbtrace_call', parentGroup: 'Traces', description: 'Executes a new message call and returns a number of possible traces (Trace Mode required, and supported only on OpenEthereum & Erigon).', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: arbTraceOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'arbtrace_call', params: [], id: 1 }, inputParameters: `
    • Object - The transaction call object
      • from: DATA, 20 Bytes - (optional) The address the transaction is sent from.
      • to: DATA, 20 Bytes - The address the transaction is directed to.
      • gas: QUANTITY - (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions.
      • gasPrice: QUANTITY - (optional) Integer of the gasPrice used for each paid gas.
      • value: QUANTITY - (optional) Integer of the value sent with this transaction.
      • data: DATA - (optional) Hash of the method signature and encoded parameters.
    • Array - Type of trace, one or more of:
      • vmTrace: To get a full trace of virtual machine's state during the execution of the given of given transaction, including for any subcalls.
      • trace: To get the basic trace of the given transaction.
      • statediff: To get information on altered Ethereum state due to execution of the given transaction.
    • QUANTITY|TAG - integer block number, or the string "latest", "earliest" or "pending", OR the blockHash (in accordance with EIP-1898)
    `, exampleParameters: `[ { "from":null, "to":"0x6b175474e89094c44da98b954eedeac495271d0f", "data":"0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE" }, ["trace"], "latest" ]` }, { name: 'Arbtrace Call Many (arbtrace_callMany)', value: 'arbtrace_callMany', parentGroup: 'Traces', description: 'Performs multiple call traces on top of the same block. i.e. transaction n will be executed on top of a pending block with all n-1 transactions applied (traced) first. Allows to trace dependent transactions. (Trace Mode required, and supported only on OpenEthereum & Erigon).', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: arbTraceOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'arbtrace_callMany', params: [], id: 1 }, inputParameters: `
    • Array - Call array, one or more of:
      • Object - The transaction call object
        • from: DATA, 20 Bytes - (optional) The address the transaction is sent from.
        • to: DATA, 20 Bytes - The address the transaction is directed to.
        • gas: QUANTITY - (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions.
        • gasPrice: QUANTITY - (optional) Integer of the gasPrice used for each paid gas.
        • value: QUANTITY - (optional) Integer of the value sent with this transaction.
        • data: DATA - (optional) Hash of the method signature and encoded parameters.
      • Array - Type of trace, one or more of:
        • vmTrace: To get a full trace of virtual machine's state during the execution of the given of given transaction, including for any subcalls.
        • trace: To get the basic trace of the given transaction.
        • statediff: To get information on altered Ethereum state due to execution of the given transaction.
    • QUANTITY|TAG - integer block number, or the string "latest", "earliest" or "pending", OR the blockHash (in accordance with EIP-1898)
    `, exampleParameters: `[ [ [ { "from":"0x407d73d8a49eeb85d32cf465507dd71d507100c1", "to":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", "value":"0x186a0" }, ["trace"] ], [ { "from":"0x407d73d8a49eeb85d32cf465507dd71d507100c1", "to":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", "value":"0x186a0" }, ["trace"] ] ], "latest" ]` }, { name: 'Arbtrace Filter (arbtrace_filter)', value: 'arbtrace_filter', parentGroup: 'Traces', description: 'Returns traces matching given filter (Trace Mode required, and supported only on OpenEthereum & Erigon).', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: arbTraceOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'arbtrace_filter', params: [], id: 1 }, inputParameters: `
    • Object - The filter object
      • fromBlock: QUANTITY|TAG - (optional) integer block number, or the string "latest", "earliest" or "pending", OR the blockHash (in accordance with EIP-1898)
      • toBlock: QUANTITY|TAG - (optional) integer block number, or the string "latest", "earliest" or "pending", OR the blockHash (in accordance with EIP-1898)
      • fromaddress: Array - (optional) Addresses of the Senders.
      • toaddress: Array - (optional) Addresses of the Receivers.
      • after: QUANTITY - (optional) The offset trace number.
      • count: QUANTITY - (optional) Integer number of traces to display in a batch.
    `, exampleParameters: `[ { "fromBlock":"0xccb943", "toBlock":"latest", "fromAddress":["0xEdC763b3e418cD14767b3Be02b667619a6374076"] } ]` }, { name: 'Arbtrace Raw Transaction (arbtrace_rawTransaction)', value: 'arbtrace_rawTransaction', parentGroup: 'Traces', description: 'Traces a call to eth_sendRawTransaction without making the call, returning the traces(Trace Mode required, and supported only on OpenEthereum & Erigon).', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: arbTraceOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'arbtrace_rawTransaction', params: [], id: 1 }, inputParameters: `
    • Data - Raw transaction data/string.
    • Array - Type of trace, one or more of:
      • vmTrace: To get a full trace of virtual machine's state during the execution of the given of given transaction, including for any subcalls.
      • trace: To get the basic trace of the given transaction.
      • statediff: To get information on altered Ethereum state due to execution of the given transaction.
    `, exampleParameters: `[ "0x02f8740181948459682f0085275c2c9f8b82520894885885521990b53fd00556c143ea056dd2f62a128808cc0c47d9477f9080c080a037437ba52140dbac1d7dc65cdb58531e038930c82314817f91cb8d8ea36a2bd0a001e134479d567b8595d77f61106cad34e62ed356d6971bc08fe0363a0696dd94", ["trace"] ]` }, { name: 'Arbtrace Replay Block Transactions (arbtrace_replayBlockTransactions)', value: 'arbtrace_replayBlockTransactions', parentGroup: 'Traces', description: 'Replays all transactions in a block returning the requested traces for each transaction (Trace Mode required, and supported only on OpenEthereum & Erigon).', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: arbTraceOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'arbtrace_replayBlockTransactions', params: [], id: 1 }, inputParameters: `
    • QUANTITY|TAG - integer block number, or the string "latest", "earliest" or "pending", OR the blockHash (in accordance with EIP-1898)
    • Array - Type of trace, one or more of:
      • vmTrace: To get a full trace of virtual machine's state during the execution of the given of given transaction, including for any subcalls.
      • trace: To get the basic trace of the given transaction.
      • statediff: To get information on altered Ethereum state due to execution of the given transaction.
    `, exampleParameters: `[ "0xccb93d", ["trace"] ]` }, { name: 'Arbtrace Replay Transaction (arbtrace_replayTransaction)', value: 'arbtrace_replayTransaction', parentGroup: 'Traces', description: 'Replays a transaction, returning the traces (Trace Mode required, and supported only on OpenEthereum & Erigon).', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: arbTraceOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'arbtrace_replayTransaction', params: [], id: 1 }, inputParameters: `
    • HASH - The hash of a transaction.
    • Array - Type of trace, one or more of:
      • vmTrace: To get a full trace of virtual machine's state during the execution of the given of given transaction, including for any subcalls.
      • trace: To get the basic trace of the given transaction.
      • statediff: To get information on altered Ethereum state due to execution of the given transaction.
    `, exampleParameters: `[ "0x3277c743c14e482243862c03a70e83ccb52e25cb9e54378b20a8303f15cb985d", ["trace"] ]` }, { name: 'Arbtrace Transaction (arbtrace_transaction)', value: 'arbtrace_transaction', parentGroup: 'Traces', description: 'Returns all traces of given transaction(Trace Mode required, and supported only on OpenEthereum & Erigon).', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: arbTraceOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'arbtrace_transaction', params: [], id: 1 }, inputParameters: `
    • HASH - The hash of a transaction.
    `, exampleParameters: `[ "0x3277c743c14e482243862c03a70e83ccb52e25cb9e54378b20a8303f15cb985d" ]` } ] as IETHOperation[] export const traceOperationsNetworks = [NETWORK.MAINNET, NETWORK.GÖRLI, NETWORK.GNOSIS, NETWORK.BSC, NETWORK.BSC_TESTNET, NETWORK.FANTOM] export const traceOperations = [ { name: 'Trace Block (trace_block)', value: 'trace_block', parentGroup: 'Traces', description: 'Returns traces created at given block (Trace Mode required, and supported only on OpenEthereum & Erigon).', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: traceOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'trace_block', params: [], id: 1 }, inputParameters: `
    • QUANTITY|TAG - integer block number, or the string "latest", "earliest" or "pending", OR the blockHash (in accordance with EIP-1898)
    `, exampleParameters: `[ "0xccb93d" ]` }, { name: 'Trace Call (trace_call)', value: 'trace_call', parentGroup: 'Traces', description: 'Executes a new message call and returns a number of possible traces (Trace Mode required, and supported only on OpenEthereum & Erigon).', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: traceOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'trace_call', params: [], id: 1 }, inputParameters: `
    • Object - The transaction call object
      • from: DATA, 20 Bytes - (optional) The address the transaction is sent from.
      • to: DATA, 20 Bytes - The address the transaction is directed to.
      • gas: QUANTITY - (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions.
      • gasPrice: QUANTITY - (optional) Integer of the gasPrice used for each paid gas.
      • value: QUANTITY - (optional) Integer of the value sent with this transaction.
      • data: DATA - (optional) Hash of the method signature and encoded parameters.
    • Array - Type of trace, one or more of:
      • vmTrace: To get a full trace of virtual machine's state during the execution of the given of given transaction, including for any subcalls.
      • trace: To get the basic trace of the given transaction.
      • statediff: To get information on altered Ethereum state due to execution of the given transaction.
    • QUANTITY|TAG - integer block number, or the string "latest", "earliest" or "pending", OR the blockHash (in accordance with EIP-1898)
    `, exampleParameters: `[ { "from":null, "to":"0x6b175474e89094c44da98b954eedeac495271d0f", "data":"0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE" }, ["trace"], "latest" ]` }, { name: 'Trace Call Many (trace_callMany)', value: 'trace_callMany', parentGroup: 'Traces', description: 'Performs multiple call traces on top of the same block. i.e. transaction n will be executed on top of a pending block with all n-1 transactions applied (traced) first. Allows to trace dependent transactions. (Trace Mode required, and supported only on OpenEthereum & Erigon).', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: traceOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'trace_callMany', params: [], id: 1 }, inputParameters: `
    • Array - Call array, one or more of:
      • Object - The transaction call object
        • from: DATA, 20 Bytes - (optional) The address the transaction is sent from.
        • to: DATA, 20 Bytes - The address the transaction is directed to.
        • gas: QUANTITY - (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions.
        • gasPrice: QUANTITY - (optional) Integer of the gasPrice used for each paid gas.
        • value: QUANTITY - (optional) Integer of the value sent with this transaction.
        • data: DATA - (optional) Hash of the method signature and encoded parameters.
      • Array - Type of trace, one or more of:
        • vmTrace: To get a full trace of virtual machine's state during the execution of the given of given transaction, including for any subcalls.
        • trace: To get the basic trace of the given transaction.
        • statediff: To get information on altered Ethereum state due to execution of the given transaction.
    • QUANTITY|TAG - integer block number, or the string "latest", "earliest" or "pending", OR the blockHash (in accordance with EIP-1898)
    `, exampleParameters: `[ [ [ { "from":"0x407d73d8a49eeb85d32cf465507dd71d507100c1", "to":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", "value":"0x186a0" }, ["trace"] ], [ { "from":"0x407d73d8a49eeb85d32cf465507dd71d507100c1", "to":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", "value":"0x186a0" }, ["trace"] ] ], "latest" ]` }, { name: 'Trace Filter (trace_filter)', value: 'trace_filter', parentGroup: 'Traces', description: 'Returns traces matching given filter (Trace Mode required, and supported only on OpenEthereum & Erigon).', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: traceOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'trace_filter', params: [], id: 1 }, inputParameters: `
    • Object - The filter object
      • fromBlock: QUANTITY|TAG - (optional) integer block number, or the string "latest", "earliest" or "pending", OR the blockHash (in accordance with EIP-1898)
      • toBlock: QUANTITY|TAG - (optional) integer block number, or the string "latest", "earliest" or "pending", OR the blockHash (in accordance with EIP-1898)
      • fromaddress: Array - (optional) Addresses of the Senders.
      • toaddress: Array - (optional) Addresses of the Receivers.
      • after: QUANTITY - (optional) The offset trace number.
      • count: QUANTITY - (optional) Integer number of traces to display in a batch.
    `, exampleParameters: `[ { "fromBlock":"0xccb943", "toBlock":"latest", "fromAddress":["0xEdC763b3e418cD14767b3Be02b667619a6374076"] } ]` }, { name: 'Trace Raw Transaction (trace_rawTransaction)', value: 'trace_rawTransaction', parentGroup: 'Traces', description: 'Traces a call to eth_sendRawTransaction without making the call, returning the traces(Trace Mode required, and supported only on OpenEthereum & Erigon).', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: traceOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'trace_rawTransaction', params: [], id: 1 }, inputParameters: `
    • Data - Raw transaction data/string.
    • Array - Type of trace, one or more of:
      • vmTrace: To get a full trace of virtual machine's state during the execution of the given of given transaction, including for any subcalls.
      • trace: To get the basic trace of the given transaction.
      • statediff: To get information on altered Ethereum state due to execution of the given transaction.
    `, exampleParameters: `[ "0x02f8740181948459682f0085275c2c9f8b82520894885885521990b53fd00556c143ea056dd2f62a128808cc0c47d9477f9080c080a037437ba52140dbac1d7dc65cdb58531e038930c82314817f91cb8d8ea36a2bd0a001e134479d567b8595d77f61106cad34e62ed356d6971bc08fe0363a0696dd94", ["trace"] ]` }, { name: 'Trace Replay Block Transactions (trace_replayBlockTransactions)', value: 'trace_replayBlockTransactions', parentGroup: 'Traces', description: 'Replays all transactions in a block returning the requested traces for each transaction (Trace Mode required, and supported only on OpenEthereum & Erigon).', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: traceOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'trace_replayBlockTransactions', params: [], id: 1 }, inputParameters: `
    • QUANTITY|TAG - integer block number, or the string "latest", "earliest" or "pending", OR the blockHash (in accordance with EIP-1898)
    • Array - Type of trace, one or more of:
      • vmTrace: To get a full trace of virtual machine's state during the execution of the given of given transaction, including for any subcalls.
      • trace: To get the basic trace of the given transaction.
      • statediff: To get information on altered Ethereum state due to execution of the given transaction.
    `, exampleParameters: `[ "0xccb93d", ["trace"] ]` }, { name: 'Trace Replay Transaction (trace_replayTransaction)', value: 'trace_replayTransaction', parentGroup: 'Traces', description: 'Replays a transaction, returning the traces (Trace Mode required, and supported only on OpenEthereum & Erigon).', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: traceOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'trace_replayTransaction', params: [], id: 1 }, inputParameters: `
    • HASH - The hash of a transaction.
    • Array - Type of trace, one or more of:
      • vmTrace: To get a full trace of virtual machine's state during the execution of the given of given transaction, including for any subcalls.
      • trace: To get the basic trace of the given transaction.
      • statediff: To get information on altered Ethereum state due to execution of the given transaction.
    `, exampleParameters: `[ "0x3277c743c14e482243862c03a70e83ccb52e25cb9e54378b20a8303f15cb985d", ["trace"] ]` }, { name: 'Trace Transaction (trace_transaction)', value: 'trace_transaction', parentGroup: 'Traces', description: 'Returns all traces of given transaction(Trace Mode required, and supported only on OpenEthereum & Erigon).', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: traceOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'trace_transaction', params: [], id: 1 }, inputParameters: `
    • HASH - The hash of a transaction.
    `, exampleParameters: `[ "0x3277c743c14e482243862c03a70e83ccb52e25cb9e54378b20a8303f15cb985d" ]` }, ...arbTraceOperations ] as IETHOperation[] export const nftOperationsNetworks = [NETWORK.MAINNET, NETWORK.GÖRLI, NETWORK.BSC, NETWORK.BSC_TESTNET] export const solanaNetworks = [NETWORK.SOLANA, NETWORK.SOLANA_DEVNET, NETWORK.SOLANA_TESTNET] export const nftOperations = [ { name: 'Fetch NFT Collection Details (qn_fetchNFTCollectionDetails)', value: 'qn_fetchNFTCollectionDetails', parentGroup: 'NFT', description: 'Returns collection details for specified contracts.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: nftOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'qn_fetchNFTCollectionDetails', params: {}, id: 1 }, inputParameters: `
    • Object - The filter object
      • contracts: (Array of Strings) - List of NFT contract addresses you'd like to get collection details data from. You may include up to 10 contract addresses per request.
    `, exampleParameters: `{ "contracts": [ "0x60E4d786628Fea6478F785A6d7e704777c86a7c6", "0x7Bd29408f11D2bFC23c34f18275bBf23bB716Bc7" ] }` }, { name: 'Fetch NFTs (qn_fetchNFTs)', value: 'qn_fetchNFTs', parentGroup: 'NFT', description: 'Returns aggregated data on NFTs for a given wallet.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: [...nftOperationsNetworks, ...solanaNetworks] }, method: 'POST', body: { jsonrpc: '2.0', method: 'qn_fetchNFTs', params: {}, id: 67 }, inputParameters: `
    • Object - The filter object
      • wallet: String - The wallet address to check for NFTs.
      • contracts: Array of Strings - (optional) List of NFT contract addresses you'd like to get ownership data from. Contract addresses may be optionally suffixed with :tokenId to specify a specific NFT id to filter on. For example, 0x2106c...7aeaa:1234 will filter Loopy Donuts on the NFT token with id 1234 only. You may include up to 20 contract addresses per request.
      • omitFields: Array of Strings - (optional) Optionally omit specific properties of objects from the "assets" array of the response. Any property of the asset object can be omitted. If omitFields is not included in the request, response will return all available fields by default.
      • page: Integer - (optional) The page number you would like returned. Page numbers start at 1 and end at "totalPages". If omitted, defaults to the first page (page 1). If the page number requested is less than 1 or higher than "totalPages", an empty assets array will be returned.
      • perPage: Integer - (optional) The maximum amount of NFT assets to return on each page. You can request up to 40 items per page. If omitted, defaults to 20 items per page.
    `, exampleParameters: `{ "wallet": "0x91b51c173a4bdaa1a60e234fc3f705a16d228740", "omitFields": [ "provenance", "traits" ], "page": 1, "perPage": 10, "contracts": [ "0x2106c00ac7da0a3430ae667879139e832307aeaa", "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D" ] }` }, { name: 'Fetch NFTs By Creator (qn_fetchNFTsByCreator)', value: 'qn_fetchNFTsByCreator', parentGroup: 'NFT', description: 'Returns aggregated data on NFTs that have been created by an address.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'qn_fetchNFTsByCreator', params: {}, id: 12 }, inputParameters: `
    • Object - The filter object
      • creator: String - The NFT creator's wallet address to check for.
      • page: Integer - (optional) The page number you would like returned. Page numbers start at 1 and end at "totalPages". If omitted, defaults to the first page (page 1). If the page number requested is less than 1 or higher than "totalPages", an empty assets array will be returned.
      • perPage: Integer - (optional) The maximum amount of NFT assets to return on each page. You can request up to 40 items per page. If omitted, defaults to 20 items per page.
    `, exampleParameters: `[ { "creator": "DznU28LgherhU2JwC2db3KmAeWPqoF9Yx2aVtNUudW6R", "page": 1, "perPage": 3 } ]` }, { name: 'Request Airdrop (requestAirdrop)', value: 'requestAirdrop', parentGroup: 'NFT', description: 'Requests an airdrop of lamports to a Pubkey (does not work on mainnet-beta).', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'requestAirdrop', params: {}, id: 1 }, inputParameters: `
    • String - Pubkey of program, as base-58 encoded string
    • Integer - lamports, as a u64
    • Commitment: ENUM - (optional) All fields are strings.
      • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • Processed - the node will query its most recent block. Note that the block may not be complete.
    `, exampleParameters: `[ "YOUR_WALLET_ADDRESS", 1000000000 ]` }, { name: 'Fetch NFT Tokens by Collection (qn_fetchNFTsByCollection)', value: 'qn_fetchNFTsByCollection', parentGroup: 'NFT', description: 'Returns aggregated data on NFTs within a given collection.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: nftOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'qn_fetchNFTsByCollection', params: {}, id: 67 }, inputParameters: `
    • Object - The filter object
      • collection: String - The contract address of the NFT Collection.
      • tokens: Array of Strings - (optional) Optionally limit the results to specific tokens within a collection. You may submit up to 20 token IDs in this parameter. If you do not want to limit results to specific tokens, please omit this parameter from your request.
      • omitFields: Array of Strings - (optional) Optionally omit specific properties of objects from the "tokens" array of the response. Any property of the tokens object can be omitted. If omitFields is not included in the request, response will return all available fields by default.
      • page: Integer - (optional) The page number you would like returned. Page numbers start at 1 and end at "totalPages". If omitted, defaults to the first page (page 1). If the page number requested is higher than "totalPages", an empty assets array will be returned. If the page number requested is less than 1, an invalid params response will be returned.
      • perPage: Integer - (optional) The maximum amount of NFT tokens to return on each page. You can request up to 100 items per page. If omitted, defaults to 40 items per page.
    `, exampleParameters: `{ "collection": "0x60E4d786628Fea6478F785A6d7e704777c86a7c6", "omitFields": [ "imageUrl", "traits" ], "page": 1, "perPage": 10 }` }, { name: 'Fetch transfers by NFT (qn_getTransfersByNFT)', value: 'qn_getTransfersByNFT', parentGroup: 'NFT', description: 'Returns transfers by given NFT.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: nftOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'qn_getTransfersByNFT', params: {}, id: 67 }, inputParameters: `
    • Object - The filter object
      • collection: String - The contract address of the NFT Collection.
      • collectionTokenId: String - The Token ID of this NFT under this collection.
      • page: Integer - (optional) The page number you would like returned. Page numbers start at 1 and end at "totalPages". If omitted, defaults to the first page (page 1). If the page number requested is higher than "totalPages", an empty assets array will be returned. If the page number requested is less than 1, an invalid params response will be returned.
      • perPage: Integer - (optional) The maximum amount of NFT tokens to return on each page. You can request up to 100 items per page. If omitted, defaults to 40 items per page.
    `, exampleParameters: `{ "collection": "0x60E4d786628Fea6478F785A6d7e704777c86a7c6", "collectionTokenId": "1", "page": 1, "perPage": 10 }` }, { name: 'Verify NFTs Owner (qn_verifyNFTsOwner)', value: 'qn_verifyNFTsOwner', parentGroup: 'NFT', description: 'Confirms ownership of specified NFTs for a given wallet.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: nftOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'qn_verifyNFTsOwner', params: {}, id: 67 }, inputParameters: `
    • wallet: String - The wallet address to check for NFTs.
    • contracts: Array of Strings - List of ERC-721 and/or ERC-1155 NFT contract addresses. Contract addresses may be optionally suffixed with :tokenId to specify a specific NFT id to filter on. For example, 0x2106c...7aeaa:1234 will will verify ownership of Loopy Donuts' NFT token with ID 1234 only. You may include up to 20 contract addresses per request.
    `, exampleParameters: `[ "0x91b51c173a4bdaa1a60e234fc3f705a16d228740", [ "0x2106c00ac7da0a3430ae667879139e832307aeaa:3643", "0xd07dc4262bcdbf85190c01c996b4c06a461d2430:133803" ] ]` } ] as IETHOperation[] export const tokenOperationsNetworks = [NETWORK.MAINNET, NETWORK.GÖRLI, NETWORK.BSC, NETWORK.BSC_TESTNET] export const tokenOperations = [ { name: 'Fetch Fungible Token Metadata by Contract Address (qn_getTokenMetadataByContractAddress)', value: 'qn_getTokenMetadataByContractAddress', parentGroup: 'Token', description: 'Returns token details for specified contract.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: tokenOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'qn_getTokenMetadataByContractAddress', params: {}, id: 67 }, inputParameters: `
    • Object - The filter object
      • contract: String - The ERC-20 contract address you'd like to get token details for.
    `, exampleParameters: `{ "contract": "0x4d224452801ACEd8B2F0aebE155379bb5D594381" }` }, { name: 'Fetch Fungible Token Metadata by Symbol (qn_getTokenMetadataBySymbol)', value: 'qn_getTokenMetadataBySymbol', parentGroup: 'Token', description: 'Returns token details for specified token symbol.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: tokenOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'qn_getTokenMetadataBySymbol', params: {}, id: 67 }, inputParameters: `
    • Object - The filter object
      • symbol: String - The ERC-20 token symbol you'd like to get details for.
      • page: Integer - (optional) The page number you would like returned. Page numbers start at 1 and end at "totalPages". If omitted, defaults to the first page (page 1). If the page number requested is higher than "totalPages", an empty tokens array will be returned. If the page number requested is less than 1, an invalid params response will be returned.
      • perPage: Integer - (optional) The maximum amount of NFT tokens to return on each page. You can request up to 100 items per page. If omitted, defaults to 40 items per page.
    `, exampleParameters: `{ "symbol": "USDC" }` }, { name: 'Fetch Fungible Tokens and Balances by Wallet (qn_getWalletTokenBalance)', value: 'qn_getWalletTokenBalance', parentGroup: 'Token', description: 'Returns ERC-20 tokens and token balances within a wallet.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: tokenOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'qn_getWalletTokenBalance', params: {}, id: 67 }, inputParameters: `
    • Object - The filter object
      • wallet: String - The wallet address to check for ERC-20 tokens.
      • contracts: (Array of Strings) - (optional) List of ERC-20 contract addresses to filter wallet balance results on. You may include up to 100 contract addresses per request.
    `, exampleParameters: `{ "wallet": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045" }` }, { name: 'Fetch Wallet Transactions by Fungible Token (qn_getWalletTokenTransactions)', value: 'qn_getWalletTokenTransactions', parentGroup: 'Token', description: 'Returns transfers of a specified token within a specified wallet address.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: tokenOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'qn_getWalletTokenTransactions', params: {}, id: 67 }, inputParameters: `
    • Object - The filter object
      • address: String - The wallet address we want to check for transfers.
      • contract: String - The ERC-20 contract we want to check for transfers.
      • fromBlock: String - (optional) First block number to check for transfers (inclusive). If omitted, will default to the genesis block for the provided token contract address.
      • toBlock: String - (optional) Last block number to check for transfers (inclusive). If omitted, will default to the latest block.
      • page: Integer - (optional) The page number you would like returned. Page numbers start at 1 and end at "totalPages". If omitted, defaults to the first page (page 1). If the page number requested is higher than "totalPages", an empty tokens array will be returned. If the page number requested is less than 1, an invalid params response will be returned.
      • perPage: Integer - (optional) The maximum amount of NFT tokens to return on each page. You can request up to 100 items per page. If omitted, defaults to 40 items per page.
    `, exampleParameters: `{ "address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", "contract": "0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE", "page": 1, "perPage": 10 }` } ] as IETHOperation[] export const avaxOperationsNetworks = [NETWORK.AVALANCHE, NETWORK.AVALANCHE_TESTNET] export const avaxOperations = [ { name: 'Avax Get Atomic Tx (avax.getAtomicTx)', value: 'avax.getAtomicTx', parentGroup: 'Avax', description: 'Returns the specified transaction.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'avax.getAtomicTx', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/C/avax', inputParameters: `
    • txID: String - The transaction ID. It should be in cb58 format.
    • encoding: String - (optional) The encoding format to use. Can be either cb58 or hex. Defaults to cb58.
    `, exampleParameters: `{ "txID":"217PBxjVznQaoPuQT8zEB86FnwoDsq6jeb2yfBgtjwYHD1RPC3", "encoding": "cb58" }` }, { name: 'Avax Get Atomic Tx Status (avax.getAtomicTxStatus)', value: 'avax.getAtomicTxStatus', parentGroup: 'Avax', description: 'Get the status of a transaction sent to the network.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'avax.getAtomicTxStatus', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/C/avax', inputParameters: `
    • txID: String - The transaction ID. It should be in cb58 format.
    `, exampleParameters: `{ "txID":"217PBxjVznQaoPuQT8zEB86FnwoDsq6jeb2yfBgtjwYHD1RPC3" }` }, { name: 'Avax Get Utx Os (avax.getUTXOs)', value: 'avax.getUTXOs', parentGroup: 'Avax', description: 'Get the UTXOs that reference a given address.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'avax.getUTXOs', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/C/avax', inputParameters: `
    • addresses: Array of Strings - A list of addresses
    • limit: Integer - (optional) The limit of UTXOs to return. If limit is omitted or greater than 1024, it is set to 1024.
    • startIndex: Object - (optional) If startIndex is omitted, will fetch all UTXOs up to limit. When using pagination (i.e when startIndex is provided), UTXOs are not guaranteed to be unique across multiple calls. That is, a UTXO may appear in the result of the first call, and then again in the second call.
      • address: String
      • utxo: String
    • sourceChain: String - The ID or alias of the chain the asset is being imported from.
    • encoding: String - (optional) Encoding sets the format for the returned UTXOs. Can be either "cb58" or "hex". Defaults to "cb58".
    `, exampleParameters: `{ "addresses": [ "C-avax1uqsts4n6j0fwmdhu4p8acmjksemtqu7vedhdks" ], "sourceChain": "X", "limit": 5, "encoding": "cb58" }` }, { name: 'Avax Issue Tx (avax.issueTx)', value: 'avax.issueTx', parentGroup: 'Avax', description: 'Send a signed transaction to the network.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'avax.issueTx', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/C/avax', inputParameters: `
    • tx: String - The signed transaction (typically signed with a library, using your private key).
    • encoding: String - (optional) Encoding sets the format for the returned UTXOs. Can be either "cb58" or "hex". Defaults to "cb58".
    `, exampleParameters: `{ "tx":"0x00", "encoding": "hex" }` }, { name: 'Avm Build Genesis (avm.buildGenesis)', value: 'avm.buildGenesis', parentGroup: 'AVM', description: 'Given a JSON representation of this Virtual Machine’s genesis state, create the byte representation of that state.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'avm.buildGenesis', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/vm/avm', inputParameters: `
    • genesisData: JSON - A JSON representing the genesis data.
    • networkID: Integer - the ID of the network
    • encoding: String - (optional) Encoding sets the format for the returned UTXOs. Can be either "cb58" or "hex". Defaults to "cb58".
    `, exampleParameters: `{ "genesisData": { "asset1": { "name": "asset1", "symbol":"MFCA", "memo": "2Zc54v4ek37TEwu4LiV3j41PUMRd6acDDU3ZCVSxE7X", "denomination": 1, "initialState": { "fixedCap" : [ { "amount":100000, "address": "local18jma8ppw3nhx5r4ap8clazz0dps7rv5u00z96u" } ] } }, "asset2": { "name": "asset2", "symbol":"MVCA", "memo": "2Zc54v4ek37TEwu4LiV3j41PUMRd6acDDU3ZCVSxE7X", "denomination": 2, "initialState": { "variableCap" : [ { "amount":100000, "address": "local18jma8ppw3nhx5r4ap8clazz0dps7rv5u00z96u" } ] } } }, "networkId": 12345, "encoding":"cb58" }` }, { name: 'Avm Get Address Txs (avm.getAddressTxs)', value: 'avm.getAddressTxs', parentGroup: 'AVM', description: 'Returns all transactions that change the balance of the given address.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'avm.getAddressTxs', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/X', inputParameters: `
    • address: String - The address for which we're fetching related transactions
    • cursor: Uint64 - (optional) The page number or offset. Leave empty to get the first page.
    • assetID: String - Only return transactions that changed the balance of this asset. Must be an ID or an alias for an asset.
    • pageSize: Uint64 - (optional) The number of items to return per page. Optional. Defaults to 1024.
    `, exampleParameters: `{ "address":"X-avax19pm62y5n0yt76wgp8pd7jdhepmppc67y7yg0cq", "assetID":"AVAX", "pageSize":20 }` }, { name: 'Avm Get All Balances (avm.getAllBalances)', value: 'avm.getAllBalances', parentGroup: 'AVM', description: 'Get the balances of all assets controlled by a given address.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'avm.getAllBalances', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/X', inputParameters: `
    • address: String - Address you want to fetch balances for.
    `, exampleParameters: `{ "address":"X-avax16902ur8dhlyxpaa0rva5fx48fhptx6ryh3dv7q" }` }, { name: 'Avm Get Asset Description (avm.getAssetDescription)', value: 'avm.getAssetDescription', parentGroup: 'AVM', description: 'Get information about an asset.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'avm.getAssetDescription', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/X', inputParameters: `
    • assetid: String - The id of the asset for which the information is requested.
    `, exampleParameters: `{ "assetID" :"AVAX" }` }, { name: 'Avm Get Balance (avm.getBalance)', value: 'avm.getBalance', parentGroup: 'AVM', description: 'Get the balance of an asset controlled by a given address.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'avm.getBalance', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/X', inputParameters: `
    • address: String - The owner of the asset.
    • assetid: String - The id of the asset for which the balance is requested.
    `, exampleParameters: `{ "address":"X-avax16902ur8dhlyxpaa0rva5fx48fhptx6ryh3dv7q", "assetID": "AVAX" }` }, { name: 'Avm Get Tx (avm.getTx)', value: 'avm.getTx', parentGroup: 'AVM', description: 'Returns the specified transaction.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'avm.getTx', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/X', inputParameters: `
    • txID: String - A specific transaction ID.
    • encoding: String - The encoding parameter sets the format of the returned transaction. Can be, "cb58", "hex" or "json". Defaults to "cb58".
    `, exampleParameters: `{ "txID":"9VBHzPDFeDBJGyhBbzakzXMHJnsYhyLEiSS6ee5AczR2oJcns", "encoding": "hex" }` }, { name: 'Avm Get Tx (avm.getTxStatus)', value: 'avm.getTxStatus', parentGroup: 'AVM', description: 'Get the status of a transaction sent to the network.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'avm.getTxStatus', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/X', inputParameters: `
    • txID: String - A specific transaction ID.
    `, exampleParameters: `{ "txID": "2HHr7xUJgWiLESV7g8T3oWfWvdpWJXd4Hctj6U3KhzUN6EsgBG" }` }, { name: 'Avm Get Utx Os (avm.getUtxOs)', value: 'avm.getUtxOs', parentGroup: 'AVM', description: 'Get the UTXOs that reference a given address.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'avm.getUtxOs', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/X', inputParameters: `
    • addresses: Array of Strings - A list of UTXOs such that each UTXO references at least one address in addresses.
    • limit: Integer - (optional) The limit of UTXOs to return. If limit is omitted or greater than 1024, it is set to 1024.
    • startIndex: Object - (optional) If startIndex is omitted, will fetch all UTXOs up to limit. When using pagination (i.e when startIndex is provided), UTXOs are not guaranteed to be unique across multiple calls. That is, a UTXO may appear in the result of the first call, and then again in the second call.
      • address: String
      • utxo: String
    • sourceChain: String - The ID or alias of the chain the asset is being imported from.
    • encoding: String - (optional) Encoding sets the format for the returned UTXOs. Can be either "cb58" or "hex". Defaults to "cb58".
    `, exampleParameters: `{ "addresses": [ "X-avax16902ur8dhlyxpaa0rva5fx48fhptx6ryh3dv7q" ], "limit": 5, "sourceChain": "X", "encoding": "hex" }` }, { name: 'Avm Issue Tx (avm.issueTx)', value: 'avm.issueTx', parentGroup: 'AVM', description: 'Send a signed transaction to the network.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'avm.issueTx', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/X', inputParameters: `
    • tx: String - The signed transaction (typically signed with a library, using your private key).
    • encoding: String - (optional) Encoding sets the format for the returned UTXOs. Can be either "cb58" or "hex". Defaults to "cb58".
    `, exampleParameters: `{ "tx":"6sTENqXfk3gahxkJbEPsmX9eJTEFZRSRw83cRJqoHWBiaeAhVbz9QV4i6SLd6Dek4eLsojeR8FbT3arFtsGz9ycpHFaWHLX69edJPEmj2tPApsEqsFd7wDVp7fFxkG6HmySR", "encoding": "cb58" }` } ] as IETHOperation[] export const fantomOperationsNetworks = [NETWORK.FANTOM] export const fantomOperations = [ { name: 'Dag Get Event (dag_getEvent)', value: 'dag_getEvent', parentGroup: 'DAG', description: 'Returns Lachesis event by hash or short ID.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: fantomOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'dag_getEvent', params: [], id: 1 }, inputParameters: `
    • eventid: String - The full event ID (hex-encoded 32 bytes) or short event ID.
    `, exampleParameters: `[ "0x00000001000000039bcda184cc9e2b20386dcee5f39fe3c4f36f7b47c297ff2b" ]` }, { name: 'Dag Get Event Payload (dag_getEventPayload)', value: 'dag_getEventPayload', parentGroup: 'DAG', description: 'Returns event (including transactions) by hash or short ID.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: fantomOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'dag_getEventPayload', params: [], id: 1 }, inputParameters: `
    • eventid: String - The full event ID (hex-encoded 32 bytes) or short event ID.
    • fulltx: Boolean - If true it returns the full transaction objects, if false only the hashes of the transactions.
    `, exampleParameters: `[ "1:3:a2395846", true ]` }, { name: 'Dag Get Heads (dag_getHeads)', value: 'dag_getHeads', parentGroup: 'DAG', description: 'Returns IDs of all the epoch events with no descendants in a given epoch.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: fantomOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'dag_getHeads', params: [], id: 1 }, inputParameters: `
    • epoch: String - epoch number encoded as a hexadecimal for a specific epoch (pass “latest” to use latest epoch; pass “pending” to use latest sealed epoch).
    `, exampleParameters: `[ "pending" ]` } ] as IETHOperation[] export const platformOperations = [ { name: 'Platform Get Balance (platform.getBalance)', value: 'platform.getBalance', parentGroup: 'Platform', description: 'Get the balance of an asset controlled by a given address.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.getBalance', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P', inputParameters: `
    • address: String - The address to get the balance of.
    `, exampleParameters: `{ "address":"P-avax1tnuesf6cqwnjw7fxjyk7lhch0vhf0v95wj5jvy" }` }, { name: 'Platform Get Blockchain Status (platform.getBlockchainStatus)', value: 'platform.getBlockchainStatus', parentGroup: 'Platform', description: 'Get the status of a blockchain.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.getBlockchainStatus', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P', inputParameters: `
    • blockchainID: String - The id of the blockchain.
    `, exampleParameters: `{ "blockchainID":"2NbS4dwGaf2p1MaXb65PrkZdXRwmSX4ZzGnUu7jm3aykgThuZE" }` }, { name: 'Platform Get Blockchains (platform.getBlockchains)', value: 'platform.getBlockchains', parentGroup: 'Platform', description: 'Get all the blockchains that exist (excluding the P-Chain).', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.getBlockchains', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P' }, { name: 'Platform Get Current Supply (platform.getCurrentSupply)', value: 'platform.getCurrentSupply', parentGroup: 'Platform', description: 'Returns an upper bound on the number of AVAX that exist. This is an upper bound because it does not account for burnt tokens, including transaction fees.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.getCurrentSupply', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P' }, { name: 'Platform Get Current Validators (platform.getCurrentValidators)', value: 'platform.getCurrentValidators', parentGroup: 'Platform', description: 'List the current validators of the given Subnet.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.getCurrentValidators', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P', inputParameters: `
    • subnetID: String - (optional) The subnet whose current validators are returned. If omitted, returns the current validators of the Primary Network.
    • nodeIDs: Array of Strings - (optional) a list of the nodeIDs of pending validators to request. If omitted, all pending validators are returned. If a specified nodeID is not in the set of pending validators, it will not be included in the response.
    ` }, { name: 'Platform Get Pending Validators (platform.getPendingValidators)', value: 'platform.getPendingValidators', parentGroup: 'Platform', description: 'List the validators in the pending validator set of the specified Subnet. Each validator is not currently validating the Subnet but will in the future.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.getPendingValidators', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P', inputParameters: `
    • subnetID: String - (optional) The subnet whose current validators are returned. If omitted, returns the current validators of the Primary Network.
    • nodeIDs: Array of Strings - (optional) a list of the nodeIDs of pending validators to request. If omitted, all pending validators are returned. If a specified nodeID is not in the set of pending validators, it will not be included in the response.
    `, exampleParameters: `{ "subnetID": null, "nodeIDs": [] }` }, { name: 'Platform Get Height (platform.getHeight)', value: 'platform.getHeight', parentGroup: 'Platform', description: 'Returns the height of the last accepted block.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.getHeight', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P' }, { name: 'Platform Get Max Stake Amount (platform.getMaxStakeAmount)', value: 'platform.getMaxStakeAmount', parentGroup: 'Platform', description: 'Get the maximum amount of AVAX required to validate the Primary Network and the maximum amount of AVAX that can be delegated.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.getMaxStakeAmount', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P', inputParameters: `
    • subnetID: String - A Buffer or cb58 string representing a subnet.
    • nodeID: String - A string representing ID of the node whose stake amount is required during the given duration.
    • startTime: Integer - A big number denoting start time of the duration during which stake amount of the node is required.
    • endTime: Integer - A big number denoting end time of the duration during which stake amount of the node is required.
    `, exampleParameters: `{ "subnetID":"11111111111111111111111111111111LpoYY", "nodeID":"NodeID-7Xhw2mDxuDS44j42TCB6U5579esbSt3Lg", "startTime": 1644240334, "endTime": 1644240634 }` }, { name: 'Platform Get Min Stake (platform.getMinStake)', value: 'platform.getMinStake', parentGroup: 'Platform', description: 'Get the minimum amount of AVAX required to validate the Primary Network and the minimum amount of AVAX that can be delegated.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.getMinStake', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P' }, { name: 'Platform Get Reward Utx Os (platform.getRewardUtxOs)', value: 'platform.getRewardUtxOs', parentGroup: 'Platform', description: `Returns the UTXOs that were rewarded after the provided transaction's staking or delegation period ended.`, providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.getRewardUtxOs', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P', inputParameters: `
    • txID: String - The ID of the staking or delegating transaction.
    • encoding: String - Specifies the format for the returned UTXOs. Can be either "cb58" or "hex" and defaults to "cb58".
    `, exampleParameters: `{ "txID": "2nmH8LithVbdjaXsxVQCQfXtzN9hBbmebrsaEYnLM9T32Uy2Y4", "encoding": "hex", }` }, { name: 'Platform Get Stake (platform.getStake)', value: 'platform.getStake', parentGroup: 'Platform', description: `Returns the staked amount for an array of addresses.`, providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.getStake', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P', inputParameters: `
    • addresses: Array of Strings - An array of address strings.
    `, exampleParameters: `{ "addresses": ["P-avax1tnuesf6cqwnjw7fxjyk7lhch0vhf0v95wj5jvy"] }` }, { name: 'Platform Get Staking Asset (platform.getStakingAssetId)', value: 'platform.getStakingAssetId', parentGroup: 'Platform', description: `Retrieve an assetID for a subnet’s staking asset. Currently this always returns the Primary Network’s staking assetID.`, providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.getStakingAssetId', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P', inputParameters: `
    • subnetID: String - (optional) the subnet whose assetID is requested.
    ` }, { name: 'Platform Get Subnets (platform.getSubnets)', value: 'platform.getSubnets', parentGroup: 'Platform', description: `Get all the Subnets that exist.`, providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.getSubnets', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P', inputParameters: `
    • ids: Array of Strings - The ids of the subnets to get information about.
    `, exampleParameters: `{ "ids":["BE5Nv8objSftNwxzcxkZGwCfVs3FPdJBio4DBwCF2A5i7RasU"] }` }, { name: 'Platform Get Timestamp (platform.getTimestamp)', value: 'platform.getTimestamp', parentGroup: 'Platform', description: `Returns the specified transaction.`, providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.getTimestamp', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P' }, { name: 'Platform Get Total Stake (platform.getTotalStake)', value: 'platform.getTotalStake', parentGroup: 'Platform', description: `Get the total amount of nAVAX staked on the Primary Network.`, providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.getTotalStake', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P' }, { name: 'Platform Get Tx (platform.getTx)', value: 'platform.getTx', parentGroup: 'Platform', description: `Returns the specified transaction.`, providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.getTx', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P', inputParameters: `
    • txID: String - The transaction ID. It should be in cb58 format.
    • encoding: String - (optional) The encoding format to use. Can be either cb58 or hex. Defaults to cb58.
    `, exampleParameters: `{ "txID":"2dum8LzyddFVZhiYBCUBZw3Xqsb2GdZAP6FsmCPB1gHGJhw1oJ", "encoding": "cb58" }` }, { name: 'Platform Get Tx Status (platform.getTxStatus)', value: 'platform.getTxStatus', parentGroup: 'Platform', description: `Returns the specified transaction.`, providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.getTxStatus', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P', inputParameters: `
    • txID: String - The transaction ID. It should be in cb58 format.
    `, exampleParameters: `{ "txID":"2dum8LzyddFVZhiYBCUBZw3Xqsb2GdZAP6FsmCPB1gHGJhw1oJ", }` }, { name: 'Platform Get Utx Os (platform.getUtxOs)', value: 'platform.getUtxOs', parentGroup: 'Platform', description: `Get the UTXOs that reference a given address.`, providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.getUtxOs', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P', inputParameters: `
    • addresses: Array of Strings - the addresses from which we want to retrieve the UTXOs.
    • limit: Integer - (optional) At most limit UTXOs are returned. If limit is omitted or greater than 1024, it is set to 1024.
    • startIndex: Object - (optional) an object of an address and a utxo.
      • address: String
      • utxo: String
    • sourceChain: String - (optional) the chain from which we want to fetch the UTXOs, (X or C).
    • encoding: String - (optional) The encoding format to use. Can be either cb58 or hex. Defaults to cb58.
    `, exampleParameters: `{ "addresses":["P-avax1tnuesf6cqwnjw7fxjyk7lhch0vhf0v95wj5jvy"], "sourceChain": "X", "limit": 5, "encoding": "hex" }` }, { name: 'Platform Get Validators At (platform.getValidatorsAt)', value: 'platform.getValidatorsAt', parentGroup: 'Platform', description: `Get the validators and their weights of a subnet or the Primary Network at a given P-Chain height.`, providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.getValidatorsAt', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P', inputParameters: `
    • height: Integer - the numerical value representing the P-Chain height to get the validator set at.
    • subnetID: String - (optional) the subnet ID to get the validator set of. If not given, gets validator set of the Primary Network.
    `, exampleParameters: `{ "height": 1, "subnetID":"11111111111111111111111111111111LpoYY" }` }, { name: 'Platform Issue Tx (platform.issueTx)', value: 'platform.issueTx', parentGroup: 'Platform', description: `Issue a transaction to the Platform Chain.`, providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.issueTx', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P', inputParameters: `
    • tx: String - the byte representation of a transaction.
    • encoding: String - (optional) The encoding format to use. Can be either cb58 or hex. Defaults to cb58.
    `, exampleParameters: `{ "tx":"0x00", "encoding": "hex" }` }, { name: 'Platform Sample Validators (platform.sampleValidators)', value: 'platform.sampleValidators', parentGroup: 'Platform', description: `Sample validators from the specified Subnet.`, providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.sampleValidators', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P', inputParameters: `
    • size: Integer - the number of validators to sample.
    • subnetID: String - (optional) the Subnet to sample from. If omitted, defaults to the Primary Network.
    `, exampleParameters: `{ "size": 2 }` }, { name: 'Platform Validated By (platform.validatedBy)', value: 'platform.validatedBy', parentGroup: 'Platform', description: `Get the Subnet that validates a given blockchain.`, providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.validatedBy', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P', inputParameters: `
    • blockchainID: String - the blockchain’s ID.
    `, exampleParameters: `{ "blockchainID": "2H1pfQqnJq1NEw1YK6CWdoH2ucrvaET8renpkxChLtnNWGYrYa" }` }, { name: 'Platform Validates (platform.validates)', value: 'platform.validates', parentGroup: 'Platform', description: `Get the IDs of the blockchains a Subnet validates.`, providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: avaxOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'platform.validates', params: {}, id: 1 }, overrideUrl: 'http://sample-endpoint-name.network.quiknode.pro/token-goes-here/ext/bc/P', inputParameters: `
    • subnetID: String - the subnet ID.
    `, exampleParameters: `{ "subnetID":"BE5Nv8objSftNwxzcxkZGwCfVs3FPdJBio4DBwCF2A5i7RasU" }` } ] as IETHOperation[] ================================================ FILE: packages/components/nodes/QuickNode/solanaOperation.ts ================================================ import { NETWORK, NETWORK_PROVIDER } from '../../src/ChainNetwork' import { IETHOperation } from '../../src/ETHOperations' export const solanaOperationsNetworks = [NETWORK.SOLANA, NETWORK.SOLANA_DEVNET, NETWORK.SOLANA_TESTNET] export const solanaOperations = [ { name: 'Get Account Info (getAccountInfo)', value: 'getAccountInfo', parentGroup: 'Account Information', description: 'Returns all information associated with the account of provided Pubkey.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getAccountInfo', params: [], id: 1 }, inputParameters: `
    • String - Pubkey of account to query, as base-58 encoded string.
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
      • encoding - encoding for Account data, either "base58" (slow), "base64", "base64+zstd", or "jsonParsed".
      • dataSlice - (optional) limit the returned account data using the provided offset: 'usize' and length: 'usize' fields; only available for "base58", "base64" or "base64+zstd" encodings.
    `, exampleParameters: `[ "vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg", {"encoding": "base58"} ]` }, { name: 'Get Balance (getBalance)', value: 'getBalance', parentGroup: 'Account Information', description: 'Returns the balance of the account of provided Pubkey.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getBalance', params: [], id: 1 }, inputParameters: `
    • String - Pubkey of account to query, as base-58 encoded string.
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
    `, exampleParameters: `[ "vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg" ]` }, { name: 'Get Block (getBlock)', value: 'getBlock', parentGroup: 'Getting Blocks', description: 'Returns identity and transaction information about a confirmed block in the ledger.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getBlock', params: [], id: 1 }, inputParameters: `
    • slot_number - slot, as u64 (64-bit unsigned integer) integer.
    • Object - (optional) Configuration object containing the following optional fields:
      • encoding - (optional) encoding for each returned Transaction, either "json", "jsonParsed", "base58" (slow), "base64". If parameter not provided, the default encoding is "json".
      • transactionDetails - (optional) level of transaction detail to return, either "full", "signatures", or "none". If parameter not provided, the default detail level is "full".
      • rewards - (optional) Boolean value, whether to populate the rewards array. If parameter not provided, the default includes rewards.
      • maxSupportedTransactionVersion - (optional) set the max transaction version to return in responses. If the requested block contains a transaction with a higher version, an error will be returned.
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
    `, exampleParameters: `[ 94101948, { "encoding": "json", "transactionDetails":"full", "rewards":false } ]` }, { name: 'Get Block Commitment (getBlockCommitment)', value: 'getBlockCommitment', parentGroup: 'Getting Blocks', description: 'Returns commitment for particular block.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getBlockCommitment', params: [], id: 1 }, inputParameters: `
    • slot_number - slot, as u64 (64-bit unsigned integer) integer.
    `, exampleParameters: `[ 94101948 ]` }, { name: 'Get Block Height (getBlockHeight)', value: 'getBlockHeight', parentGroup: 'Getting Blocks', description: 'Returns the latest block number of the blockchain.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getBlockHeight', params: [], id: 1 }, inputParameters: `
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
    ` }, { name: 'Get Block Production (getBlockProduction)', value: 'getBlockProduction', parentGroup: 'Getting Blocks', description: 'Returns recent block production information from the current or previous epoch.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getBlockProduction', params: [], id: 1 }, inputParameters: `
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
      • range: ENUM - (optional) Slot range to return block production for. If parameter not provided, defaults to current epoch.
        • firstSlot - first slot to return block production information for (inclusive).
        • lastSlot - (optional) last slot to return block production information for (inclusive). If parameter not provided, defaults to the highest slot.
      • identity - (optional) Only return results for this validator identity (base-58 encoded).
    ` }, { name: 'Get Block Time (getBlockTime)', value: 'getBlockTime', parentGroup: 'Getting Blocks', description: 'Returns the estimated production time of a block.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getBlockTime', params: [], id: 1 }, inputParameters: `
    • slot_number - slot, as u64 (64-bit unsigned integer) integer.
    `, exampleParameters: `[ 94101948 ]` }, { name: 'Get Blocks (getBlocks)', value: 'getBlocks', parentGroup: 'Getting Blocks', description: 'Returns a list of confirmed blocks between two slots.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getBlocks', params: [], id: 1 }, inputParameters: `
    • start_slot - Start slot, as an u64 integer.
    • end_slot - (optional) end slot as an u64 integer.
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
    `, exampleParameters: `[ 5, 10 ]` }, { name: 'Get Blocks With Limit (getBlocksWithLimit)', value: 'getBlocksWithLimit', parentGroup: 'Getting Blocks', description: 'Returns a list of confirmed blocks starting at the given slot', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getBlocksWithLimit', params: [], id: 1 }, inputParameters: `
    • start_slot - Start slot, as an u64 integer.
    • limit - limit, as u64 integer.
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
    `, exampleParameters: `[ 5, 3 ]` }, { name: 'Get Cluster Nodes (getClusterNodes)', value: 'getClusterNodes', parentGroup: 'Node Information', description: 'Returns information about all the nodes participating in the cluster.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getClusterNodes', params: [], id: 1 } }, { name: 'Get Confirmed Block (getConfirmedBlock)', value: 'getConfirmedBlock', parentGroup: 'Getting Blocks', description: 'Returns identity and transaction information about a confirmed block in the ledger.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getConfirmedBlock', params: [], id: 1 }, inputParameters: `
    • slot_number - slot, as u64 (64-bit unsigned integer) integer.
    • limit - limit, as u64 integer.
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
      • encoding - (optional) encoding for each returned Transaction, either "json", "jsonParsed", "base58" (slow), "base64". If parameter not provided, the default encoding is "json".
      • transactionDetails - (optional) level of transaction detail to return, either "full", "signatures", or "none". If parameter not provided, the default detail level is "full".
      • rewards - (optional) Boolean value, whether to populate the rewards array. If parameter not provided, the default includes rewards.
    `, exampleParameters: `[ 94101948, { "encoding": "json", "transactionDetails":"full", "rewards":false } ]` }, { name: 'Get Confirmed Blocks (getConfirmedBlocks)', value: 'getConfirmedBlocks', parentGroup: 'Getting Blocks', description: 'Returns a list of confirmed blocks between two slots.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getConfirmedBlocks', params: [], id: 1 }, inputParameters: `
    • start_slot - start_slot, as u64 (64-bit unsigned integer).
    • end_slot - (optional) end_slot, as u64 (64-bit unsigned integer).
    • Commitment: ENUM - (optional) All fields are strings.
      • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • Processed - the node will query its most recent block. Note that the block may not be complete.
    `, exampleParameters: `[ 94101945, 94101948 ]` }, { name: 'Get Confirmed Blocks With Limit (getConfirmedBlocksWithLimit)', value: 'getConfirmedBlocksWithLimit', parentGroup: 'Getting Blocks', description: 'Returns a list of confirmed blocks starting at the given slot.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getConfirmedBlocksWithLimit', params: [], id: 1 }, inputParameters: `
    • start_slot - start_slot, as u64 (64-bit unsigned integer).
    • limit - (optional) limit, as u64 (64-bit unsigned integer).
    • Commitment: ENUM - (optional) All fields are strings.
      • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • Processed - the node will query its most recent block. Note that the block may not be complete.
    `, exampleParameters: `[ 94101945, 3 ]` }, { name: 'Get Confirmed Signatures For Address2 (getConfirmedSignaturesForAddress2)', value: 'getConfirmedSignaturesForAddress2', parentGroup: 'Reading & Writing Transactions', description: 'Returns confirmed signatures for transactions involving an address backwards in time from the provided signature or most recent confirmed block.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getConfirmedSignaturesForAddress2', params: [], id: 1 }, inputParameters: `
    • acc_add - account address as base-58 encoded string.
    • Object - (optional) Configuration object containing the following optional fields:
      • limit - (optional) maximum transaction signatures to return (between 1 and 1,000, default: 1,000).
      • before - (optional) start searching backwards from this transaction signature. If not provided the search starts from the top of the highest max confirmed block.
      • until - (optional) search until this transaction signature, if found before limit reached.
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
    `, exampleParameters: `[ "Vote111111111111111111111111111111111111111", {"limit": 1} ]` }, { name: 'Get Confirmed Transaction (getConfirmedTransaction)', value: 'getConfirmedTransaction', parentGroup: 'Reading & Writing Transactions', description: 'Returns transaction details for a confirmed transaction.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getConfirmedTransaction', params: [], id: 1 }, inputParameters: `
    • tx_sig - transaction signature as base-58 encoded string.
    • Object - (optional) Configuration object containing the following optional fields:
      • encoding - Tencoding for each returned Transaction, either "json", "jsonParsed", "base58" (slow), "base64".
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
    `, exampleParameters: `[ "3Pdh1xgS7HYXcPquN1JQQXs8C6Tn2ZTkHg86wXMwDEEnJxVVZsE3WgxHSx258boUtHcMVkKCGbT9dYWWfk7CWV2m", { "encoding": "json", } ]` }, { name: 'Get Epoch Info (getEpochInfo)', value: 'getEpochInfo', parentGroup: 'Network Information', description: 'Returns information about the current epoch.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getEpochInfo', params: [], id: 1 }, inputParameters: `
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
    ` }, { name: 'Get Epoch Schedule (getEpochSchedule)', value: 'getEpochSchedule', parentGroup: 'Network Information', description: `Returns epoch schedule information from this cluster's genesis config.`, providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getEpochSchedule', params: [], id: 1 } }, { name: 'Get Fee Calculator For Blockhash (getFeeCalculatorForBlockhash)', value: 'getFeeCalculatorForBlockhash', parentGroup: 'Network Information', description: 'Returns the fee calculator associated with the query blockhash, or null if the blockhash has expired.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getFeeCalculatorForBlockhash', params: [], id: 1 }, inputParameters: `
    • string - (optional) query blockhash as a Base58 encoded string.
    • Commitment: ENUM - (optional) All fields are strings.
      • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • Processed - the node will query its most recent block. Note that the block may not be complete.
    `, exampleParameters: `["6EUDAG2UBZ1J7CbpixutsELc5c6s4k8YzaWawyKH2Pit"]` }, { name: 'Get Fee For Message (getFeeForMessage)', value: 'getFeeForMessage', parentGroup: 'Network Information', description: 'Get the fee the network will charge for a particular message.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getFeeForMessage', params: [], id: 1 }, inputParameters: `
    • Message - Base-64 encoded Message
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
    `, exampleParameters: `[ "AQABAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAA", { "commitment": "processed" } ]` }, { name: 'Get Fee Rate Governor (getFeeRateGovernor)', value: 'getFeeRateGovernor', parentGroup: 'Network Information', description: 'Returns the fee rate governor information from the root bank.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getFeeRateGovernor', params: [], id: 1 } }, { name: 'Get Fees (getFees)', value: 'getFees', parentGroup: 'Network Information', description: 'Returns a recent block hash from the ledger, a fee schedule that can be used to compute the cost of submitting a transaction using it, and the last slot in which the blockhash will be valid.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getFees', params: [], id: 1 }, inputParameters: `
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
    `, exampleParameters: `[ { "commitment": "processed" } ]` }, { name: 'Get First Available Block (getFirstAvailableBlock)', value: 'getFirstAvailableBlock', parentGroup: 'Getting Blocks', description: 'Returns the slot of the lowest confirmed block that has not been purged from the ledger', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getFirstAvailableBlock', params: [], id: 1 } }, { name: 'Get Genesis Hash (getGenesisHash)', value: 'getGenesisHash', parentGroup: 'Network Information', description: 'Returns the genesis hash.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getGenesisHash', params: [], id: 1 } }, { name: 'Get Health (getHealth)', value: 'getHealth', parentGroup: 'Node Information', description: 'Returns the current health of the node.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getHealth', params: [], id: 1 } }, { name: 'Get Highest Snapshot Slot (getHighestSnapshotSlot)', value: 'getHighestSnapshotSlot', parentGroup: 'Node Information', description: 'Returns the highest slot that the node has a snapshot for. This will find the highest full snapshot slot, and the highest incremental snapshot slot based on the full snapshot slot, if there is one.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getHighestSnapshotSlot', params: [], id: 1 } }, { name: 'Get Identity (getIdentity)', value: 'getIdentity', parentGroup: 'Node Information', description: 'Returns the identity pubkey for the current node.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getIdentity', params: [], id: 1 } }, { name: 'Get Inflation Governor (getInflationGovernor)', value: 'getInflationGovernor', parentGroup: 'Network Inflation', description: 'Returns the current inflation governor.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getInflationGovernor', params: [], id: 1 }, inputParameters: `
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
    `, exampleParameters: `[ { "commitment": "processed" } ]` }, { name: 'Get Inflation Rate (getInflationRate)', value: 'getInflationRate', parentGroup: 'Network Inflation', description: 'Returns the specific inflation values for the current epoch.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getInflationRate', params: [], id: 1 } }, { name: 'Get Inflation Reward (getInflationReward)', value: 'getInflationReward', parentGroup: 'Network Inflation', description: 'Returns the inflation / staking reward for a list of addresses for an epoch.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getInflationReward', params: [], id: 1 }, inputParameters: `
    • Array - An array of addresses to query, as base-58 encoded strings
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
    • epoch - (optional) An epoch for which the reward occurs, as u64 (64-bit unsigned integer) integer. If omitted, the previous epoch will be used.
    • minContextSlot - (optional) set the minimum slot that the request can be evaluated at.
    • maxSupportedTransactionVersion - (optional) set the max transaction version to return in responses. If the requested block contains a transaction with a higher version, an error will be returned.
    `, exampleParameters: `[ [ "ADDRESS_TO_SEARCH_1", "ADDRESS_TO_SEARCH_2" ], {"epoch": 2} ]` }, { name: 'Get Largest Accounts (getLargestAccounts)', value: 'getLargestAccounts', parentGroup: 'Account Information', description: 'Returns the 20 largest accounts, by lamport balance (results may be cached up to two hours).', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getLargestAccounts', params: [], id: 1 }, inputParameters: `
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
    • filter - (optional) filter results by account type; currently supported: circulating | nonCirculating
    ` }, { name: 'Get Latest Blockhash (getLatestBlockhash)', value: 'getLatestBlockhash', parentGroup: 'Getting Blocks', description: 'Returns the latest blockhash.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getLatestBlockhash', params: [], id: 1 }, inputParameters: `
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
    • minContextSlot - - set the minimum slot that the request can be evaluated at.
    `, exampleParameters: `[ { "commitment": "processed" } ]` }, { name: 'Get Leader Schedule (getLeaderSchedule)', value: 'getLeaderSchedule', parentGroup: 'Slot Information', description: 'Returns the leader schedule for an epoch.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getLeaderSchedule', params: [], id: 1 }, inputParameters: `
    • 64-bit unsigned integer - (optional) Fetch the leader schedule for the epoch that corresponds to the provided slot. If unspecified, the leader schedule for the current epoch is fetched.
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
      • identity - (optional) Only return results for this validator identity (base-58 encoded).
    ` }, { name: 'Get Max Retransmit Slot (getMaxRetransmitSlot)', value: 'getMaxRetransmitSlot', parentGroup: 'Slot Information', description: 'Get the max slot seen from retransmit stage.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getMaxRetransmitSlot', params: [], id: 1 } }, { name: 'Get Max Shred Insert Slot (getMaxShredInsertSlot)', value: 'getMaxShredInsertSlot', parentGroup: 'Slot Information', description: 'Get the max slot seen from after shred insert.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getMaxShredInsertSlot', params: [], id: 1 } }, { name: 'Get Minimum Balance For Rent Exemption (getMinimumBalanceForRentExemption)', value: 'getMinimumBalanceForRentExemption', parentGroup: 'Slot Information', description: 'Returns minimum balance required to make account rent exempt.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getMinimumBalanceForRentExemption', params: [], id: 1 }, inputParameters: `
    • usize - account data length
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
    `, exampleParameters: `[50]` }, { name: 'Get Multiple Accounts (getMultipleAccounts)', value: 'getMultipleAccounts', parentGroup: 'Account Information', description: 'Returns the account information for a list of Pubkeys.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getMultipleAccounts', params: [], id: 1 }, inputParameters: `
    • array - An array of Pubkeys to query, as base-58 encoded strings.
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
      • encoding - encoding for Account data, either "base58" (slow), "base64", "base64+zstd", or "jsonParsed".
      • dataSlice - (optional) limit the returned account data using the provided offset: 'usize' and length: 'usize' fields; only available for "base58", "base64" or "base64+zstd" encodings.
    `, exampleParameters: `[ [ "vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg", "4fYNw3dojWmQ4dXtSGE9epjRGy9pFSx62YypT7avPYvA" ], { "dataSlice": { "offset": 0, "length": 0 } } ]` }, { name: 'Get Program Accounts (getProgramAccounts)', value: 'getProgramAccounts', parentGroup: 'Account Information', description: 'Returns all accounts owned by the provided program Pubkey.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getProgramAccounts', params: [], id: 1 }, inputParameters: `
    • String - Pubkey of program, as base-58 encoded string
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
      • encoding - (optional) encoding for Account data, either "base58" (slow), "base64", "base64+zstd", or "jsonParsed". "base58" is limited to Account data of less than 129 bytes. "base64" will return base64 encoded data for Account data of any size. "base64+zstd" compresses the Account data using Zstandard and base64-encodes the result. "jsonParsed" encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data.
      • dataSlice - (optional) limit the returned account data using the provided offset: usize and length: usize fields; only available for "base58", "base64" or "base64+zstd" encodings.
      • filters - (optional) filter results using up to 4 filter objects; account must meet all filter criteria to be included in results.
      • withContext - (optional) wrap the result in an RpcResponse JSON object.
      • minContextSlot - (optional) set the minimum slot that the request can be evaluated at.
    `, exampleParameters: `["PROGRAM_TO_SEARCH"]` }, { name: 'Get Recent Blockhash (getRecentBlockhash)', value: 'getRecentBlockhash', parentGroup: 'Getting Blocks', description: 'Returns a recent block hash from the ledger, and a fee schedule that can be used to compute the cost of submitting a transaction using it.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getRecentBlockhash', params: [], id: 1 }, inputParameters: `
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
    `, exampleParameters: `[ { "commitment": "processed" } ]` }, { name: 'Get Recent Performance Samples (getRecentPerformanceSamples)', value: 'getRecentPerformanceSamples', parentGroup: 'Slot Information', description: 'Returns a list of recent performance samples, in reverse slot order. Performance samples are taken every 60 seconds and include the number of transactions and slots that occur in a given time window.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getRecentPerformanceSamples', params: [], id: 1 }, inputParameters: `
    • limit - (optional) number of samples to return (maximum 720).
    `, exampleParameters: `[4]` }, { name: 'Get Signature Statuses (getSignatureStatuses)', value: 'getSignatureStatuses', parentGroup: 'Reading & Writing Transactions', description: 'Returns the statuses of a list of signatures. Unless the searchTransactionHistory configuration parameter is included, this method only searches the recent status cache of signatures, which retains statuses for all active slots plus MAX_RECENT_BLOCKHASHES rooted slots.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getSignatureStatuses', params: [], id: 1 }, inputParameters: `
    • array - An array of transaction signatures to confirm, as base-58 encoded strings.
    • Object - (optional) Configuration object containing the following optional fields:
      • searchTransactionHistory - Boolean value, if true, a Solana node will search its ledger cache for any signatures not found in the recent status cache.
    `, exampleParameters: `[ [ "5tGfZLNDxtCtWsW1BJoeTyHvnfGqpADDfBkUgkKENQJ8iz5yTN3ae51j8m8GRFevJx82gyuKnEX7iexFsqf7X2vS", "D13jTJYXoQBcRY9AfT5xRtsew7ENgCkNs6mwwwAcUCp4ZZCEM7YwZ7en4tVsoDa7Gu75Jjj2FgLXNUz8Zmgedff" ], {"searchTransactionHistory": true} ]` }, { name: 'Get Signatures For Address (getSignaturesForAddress)', value: 'getSignaturesForAddress', parentGroup: 'Reading & Writing Transactions', description: 'Returns confirmed signatures for transactions involving an address backwards in time from the provided signature or most recent confirmed block.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getSignaturesForAddress', params: [], id: 1 }, inputParameters: `
    • account_address -The account address as base-58 encoded string.
    • Object - (optional) Configuration object containing the following optional fields:
      • Limit - (optional) Maximum number of transaction signatures to return (between 1 and 1,000, default: 1,000).
      • Before - (optional) Start searching backwards from this transaction signature. If not provided the search starts from the top of the highest max confirmed block.
      • Until - (optional) search until this transaction signature, if found before limit reached.
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
    `, exampleParameters: `[ "Vote111111111111111111111111111111111111111", {"limit": 1} ]` }, { name: 'Get Slot (getSlot)', value: 'getSlot', parentGroup: 'Slot Information', description: 'Returns the slot that has reached the given or default commitment level.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getSlot', params: [], id: 1 }, inputParameters: `
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
    `, exampleParameters: `[ { "commitment": "processed" } ]` }, { name: 'Get Slot Leader (getSlotLeader)', value: 'getSlotLeader', parentGroup: 'Slot Information', description: 'Returns the current slot leader.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getSlotLeader', params: [], id: 1 }, inputParameters: `
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
    `, exampleParameters: `[ {"commitment": "processed"} ]` }, { name: 'Get Slot Leaders (getSlotLeaders)', value: 'getSlotLeaders', parentGroup: 'Slot Information', description: 'Returns the slot leaders for a given slot range.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getSlotLeaders', params: [], id: 1 }, inputParameters: `
    • Start slot - Start slot, as 64-bit unsigned integer.
    • Limit - Limit, as 64-bit unsigned integer.
    `, exampleParameters: `[140630426, 10]` }, { name: 'Get Snapshot Slot (getSnapshotSlot)', value: 'getSnapshotSlot', parentGroup: 'Slot Information', description: 'Returns the highest slot that the node has a snapshot for.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getSnapshotSlot', params: [], id: 1 } }, { name: 'Get Stake Activation (getStakeActivation)', value: 'getStakeActivation', parentGroup: 'Account Information', description: 'Returns epoch activation information for a stake account.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getStakeActivation', params: [], id: 1 }, inputParameters: `
    • String - Pubkey of stake account to query, as base-58 encoded string.
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
      • epoch - (optional) epoch for which to calculate activation details. If parameter not provided, defaults to current epoch.
    `, exampleParameters: `["Buc3N8TitzhVtvy7sm85YWpY2F5PAAKV2iLP1cZAbwrJ"]` }, { name: 'Get Supply (getSupply)', value: 'getSupply', parentGroup: 'Network Information', description: 'Returns information about the current supply.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getSupply', params: [], id: 1 }, inputParameters: `
    • Commitment: ENUM - (optional) All fields are strings.
      • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • Processed - the node will query its most recent block. Note that the block may not be complete.
    ` }, { name: 'Get Token Account Balance (getTokenAccountBalance)', value: 'getTokenAccountBalance', parentGroup: 'Token Information', description: 'Returns the token balance of an SPL Token account.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getTokenAccountBalance', params: [], id: 1 }, inputParameters: `
    • String - Pubkey of account delegate to query, as base-58 encoded string.
    • Commitment: ENUM - (optional) All fields are strings.
      • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
      • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
      • Processed - the node will query its most recent block. Note that the block may not be complete.
    `, exampleParameters: `["DhzDoryP2a4rMK2bcWwJxrE2uW6ir81ES8ZwJJPPpxDN"]` }, { name: 'Get Token Accounts By Delegate (getTokenAccountsByDelegate)', value: 'getTokenAccountsByDelegate', parentGroup: 'Token Information', description: 'Returns all SPL Token accounts by approved Delegate.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getTokenAccountsByDelegate', params: [], id: 1 }, inputParameters: `
    • String - Pubkey of account delegate to query, as base-58 encoded string.
    • Object - Either:
      • mint - Pubkey of the specific token Mint to limit accounts to, as base-58 encoded string.
      • programId - Pubkey of the Token program ID that owns the accounts, as base-58 encoded string.
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
      • encoding - (encoding for Account data, either "base58" (slow), "base64", "base64+zstd" or "jsonParsed". "jsonParsed" encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If "jsonParsed" is requested but a valid mint cannot be found for a particular account, that account will be filtered out from results.
      • dataSlice - limit the returned account data using the provided offset: (unsigned integer) and length: (unsigned integer) fields; only available for "base58", "base64" or "base64+zstd" encodings.
    `, exampleParameters: `[ "4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T", { "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }, { "encoding": "jsonParsed" } ]` }, { name: 'Get Token Accounts By Owner (getTokenAccountsByOwner)', value: 'getTokenAccountsByOwner', parentGroup: 'Token Information', description: 'Returns all SPL Token accounts by token owner.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getTokenAccountsByOwner', params: [], id: 1 }, inputParameters: `
    • String - Pubkey of account delegate to query, as base-58 encoded string.
    • Object - Either:
      • mint - Pubkey of the specific token Mint to limit accounts to, as base-58 encoded string.
      • programId - Pubkey of the Token program ID that owns the accounts, as base-58 encoded string.
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
      • encoding - (encoding for Account data, either "base58" (slow), "base64", "base64+zstd" or "jsonParsed". "jsonParsed" encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If "jsonParsed" is requested but a valid mint cannot be found for a particular account, that account will be filtered out from results.
      • dataSlice - limit the returned account data using the provided offset: (unsigned integer) and length: (unsigned integer) fields; only available for "base58", "base64" or "base64+zstd" encodings.
    `, exampleParameters: `[ "GgPpTKg78vmzgDtP1DNn72CHAYjRdKY7AV6zgszoHCSa", { "mint": "1YDQ35V8g68FGvcT85haHwAXv1U7XMzuc4mZeEXfrjE" }, { "encoding": "jsonParsed" } ]` }, { name: 'Get Token Largest Accounts (getTokenLargestAccounts)', value: 'getTokenLargestAccounts', parentGroup: 'Token Information', description: 'Returns the 20 largest accounts of a particular SPL Token type.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getTokenLargestAccounts', params: [], id: 1 }, inputParameters: `
    • String - Pubkey of account delegate to query, as base-58 encoded string.
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
    `, exampleParameters: `[ "1YDQ35V8g68FGvcT85haHwAXv1U7XMzuc4mZeEXfrjE", { "commitment": "processed" } ]` }, { name: 'Get Token Supply (getTokenSupply)', value: 'getTokenSupply', parentGroup: 'Token Information', description: 'Returns the total supply of an SPL Token type.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getTokenSupply', params: [], id: 1 }, inputParameters: `
    • String - Pubkey of token Mint to query, as base-58 encoded string
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
    `, exampleParameters: `[ "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU", { "commitment": "processed" } ]` }, { name: 'Get Transaction (getTransaction)', value: 'getTransaction', parentGroup: 'Reading & Writing Transactions', description: 'Returns transaction details for a confirmed transaction.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getTransaction', params: [], id: 1 }, inputParameters: `
    • tx_sig - transaction signature as base-58 encoded string.
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
      • encoding - Tencoding for each returned Transaction, either "json", "jsonParsed", "base58" (slow), "base64". If parameter not provided, the default encoding is "json".
      • maxSupportedTransactionVersion - (optional) set the max transaction version to return in responses. If the requested transaction is a higher version, an error will be returned.
    `, exampleParameters: `[ "D13jTJYXoQBcRY9AfT5xRtsew7ENgCkNs6mwwwAcUCp4ZZCEM7YwZ7en4tVsoDa7Gu75Jjj2FgLXNUz8Zmgedff", {"encoding": "json"} ]` }, { name: 'Get Transaction Count (getTransactionCount)', value: 'getTransactionCount', parentGroup: 'Reading & Writing Transactions', description: 'Returns the current transaction count from the ledger.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getTransactionCount', params: [], id: 1 }, inputParameters: `
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
    `, exampleParameters: `[ {"commitment": "processed"} ]` }, { name: 'Get Version (getVersion)', value: 'getVersion', parentGroup: 'Network Information', description: 'Returns the current solana version running on the node.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getVersion', params: [], id: 1 } }, { name: 'Get Vote Accounts (getVoteAccounts)', value: 'getVoteAccounts', parentGroup: 'Account Information', description: 'Returns the account info and associated stake for all the voting accounts in the current bank.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'getVoteAccounts', params: [], id: 1 }, inputParameters: `
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
      • votePubkey - (optional) Only return results for this validator vote address. Passed as a string, base-58 encoded.
      • keepUnstakedDelinquents - (optional) Boolean that determines whether or not to filter out delinquent validators with no stake.
      • delinquentSlotDistance - (optional) Specify the number of slots behind the tip that a validator must fall to be considered delinquent. Passed as an integer. It is not recomended to specify this argument.
    `, exampleParameters: `[ {"commitment": "processed"} ]` }, { name: 'Is Blockhash Valid (isBlockhashValid)', value: 'isBlockhashValid', parentGroup: 'Getting Blocks', description: 'Returns whether a blockhash is still valid or not', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'isBlockhashValid', params: [], id: 1 }, inputParameters: `
    • blockhash - the blockhash of this block, as base-58 encoded string.
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
    • minContextSlot - (optional) set the minimum slot that the request can be evaluated at
    `, exampleParameters: `[ "ENTER_BLOCKHASH_ID_HERE", {"commitment": "processed"} ]` }, { name: 'Minimum Ledger Slot (minimumLedgerSlot)', value: 'minimumLedgerSlot', parentGroup: 'Slot Information', description: 'Returns the lowest slot that the node has information of in its ledger.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'minimumLedgerSlot', params: [], id: 1 } }, { name: 'Send Transaction (sendTransaction)', value: 'sendTransaction', parentGroup: 'Reading & Writing Transactions', description: 'Submits a signed transaction to the cluster for processing.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'sendTransaction', params: [], id: 1 }, inputParameters: `
    • String - fully-signed Transaction, as encoded string.
    • Object - (optional) Configuration object containing the following optional fields:
      • skipPreflight - if true, skip the preflight transaction checks (default: false).
      • preflightCommitment - (optional) Commitment level to use for preflight (default: "finalized").
      • encoding - (optional) Encoding used for the transaction data. Either "base58" (slow, DEPRECATED), or "base64". (default: "base58").
      • maxRetries - (optional).
    `, exampleParameters: `[ "ENTER_ENCODED_TRANSACTION_ID", ]` }, { name: 'Simulate Transaction (simulateTransaction)', value: 'simulateTransaction', parentGroup: 'Reading & Writing Transactions', description: 'Simulate sending a transaction.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'simulateTransaction', params: [], id: 1 }, inputParameters: `
    • String - Transaction, as an encoded string. The transaction must have a valid blockhash, but is not required to be signed.
    • Object - (optional) Configuration object containing the following optional fields:
      • sigVerify - if true the transaction signatures will be verified (default: false, conflicts with replaceRecentBlockhash).
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
      • encoding - (optional) Encoding used for the transaction data. Either "base58" (slow, DEPRECATED), or "base64". (default: "base58").
      • replaceRecentBlockhash - (optional) if true the transaction recent blockhash will be replaced with the most recent blockhash. (default: false, conflicts with sigVerify).
      • accounts : Object - (optional) Accounts configuration object containing the following fields:
        • encoding - (optional) encoding for returned Account data, either "base64" (default), "base64+zstd" or "jsonParsed". "jsonParsed" encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If "jsonParsed" is requested but a parser cannot be found, the field falls back to binary encoding, detectable when the data field is type string.
        • addresses - An array of accounts to return, as base-58 encoded strings.
    `, exampleParameters: `[ "ENTER_ENCODED_TRANSACTION_ID", ]` } ] as IETHOperation[] ================================================ FILE: packages/components/nodes/QuickNode/subscribeOperation.ts ================================================ import { NETWORK, NETWORK_PROVIDER } from '../../src/ChainNetwork' import { IETHOperation, infuraSupportedNetworks } from '../../src/ETHOperations' import { solanaOperationsNetworks } from './solanaOperation' export const subsOperationsNetworks = [ ...infuraSupportedNetworks, NETWORK.ARBITRUM_NOVA, NETWORK.BSC, NETWORK.BSC_TESTNET, NETWORK.CELO, NETWORK.GNOSIS, NETWORK.FANTOM ] export const subscribeOperations = [ { name: 'Eth Subscribe (eth_subscribe)', value: 'eth_subscribe', parentGroup: 'Subscribe', description: 'Starts a subscription to a specific event.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: subsOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'eth_subscribe', params: [] }, inputParameters: `
    • subscription name: string - The type of event you want to subscribe to (i.e., newHeads, logs, pendingTransactions, newPendingTransactions). This method supports the following subscription types:
      • pendingTransactions - Returns full transactions that are sent to the network, marked as pending, and are sent from or to a certain address.
      • newPendingTransactions - Returns the hash for all transactions that are added to the pending state and are signed with a key that is available in the node.
      • newHeads - Fires a notification each time a new header is appended to the chain, including chain reorganizations.
      • logs - Returns logs that are included in new imported blocks and match the given filter criteria.
    • data: object - (Optional) - Arguments such as an address, multiple addresses, and topics. Note, only logs that are created from these addresses or match the specified topics will return logs.
    `, exampleParameters: `["newHeads"]` }, { name: 'Account Subscribe (accountSubscribe)', value: 'accountSubscribe', parentGroup: 'Subscribe', description: '(Subscription Websocket) Subscribe to an account to receive notifications when the lamports or data for a given account public key changes.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'accountSubscribe', params: [] }, inputParameters: `
    • String - Pubkey of account to query, as base-58 encoded string.
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
      • encoding - encoding for Account data, either "base58" (slow), "base64", "base64+zstd", or "jsonParsed".
    `, exampleParameters: `[ "E645TckHQnDcavVv92Etc6xSWQaq8zzPtPRGBheviRAk", { "encoding": "base64", "commitment": "finalized" } ]` }, { name: 'Log Subscribe (logSubscribe)', value: 'logSubscribe', parentGroup: 'Subscribe', description: '(Subscription Websocket) Subscribe to transaction logging.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'logSubscribe', params: [] }, inputParameters: `
    • filters: ENUM - filter criteria for the logs to receive results by account type; currently supported
      • all - subscribe to all transactions except for simple vote transactions
      • allWithVotes - subscribe to all transactions including simple vote transactions
      • mentions - subscribe to all transactions that mention the provided Pubkey (as base-58 encoded string)
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
      • encoding - encoding for Account data, either "base58" (slow), "base64", "base64+zstd", or "jsonParsed".
    `, exampleParameters: `[ "all", { "encoding": "base64", "commitment": "finalized" } ]` }, { name: 'Program Subscribe (programSubscribe)', value: 'programSubscribe', parentGroup: 'Subscribe', description: '(Subscription Websocket) Subscribe to a program to receive notifications when the lamports or data for a given account owned by the program changes.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'programSubscribe', params: [] }, inputParameters: `
    • program_id - Pubkey, as base-58 encoded string
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
      • encoding - encoding for Account data, either "base58" (slow), "base64", "base64+zstd", or "jsonParsed".
      • filters - filter results using various filter objects; account must meet all filter criteria to be included in results.
    `, exampleParameters: `[ "11111111111111111111111111111111", { "encoding": "base64", "commitment": "finalized" } ]` }, { name: 'Root Subscribe (rootSubscribe)', value: 'rootSubscribe', parentGroup: 'Subscribe', description: '(Subscription Websocket) Subscribe to receive notification anytime a new root is set by the validator.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'rootSubscribe', params: [] } }, { name: 'Signature Subscribe (signatureSubscribe)', value: 'signatureSubscribe', parentGroup: 'Subscribe', description: '(Subscription Websocket) Subscribe to a transaction signature to receive notification when the transaction is confirmed On signatureNotification, the subscription is automatically cancelled.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'signatureSubscribe', params: [] }, inputParameters: `
    • String - Transaction Signature, as base-58 encoded string
    • Object - (optional) Configuration object containing the following optional fields:
      • Commitment: ENUM - (optional) All fields are strings.
        • Finalized - the node will query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as finalized.
        • Confirmed - the node will query the most recent block that has been voted on by supermajority of the cluster.
        • Processed - the node will query its most recent block. Note that the block may not be complete.
    `, exampleParameters: `[ "51y9Hf2cFzrUPDH24qvL6b6PtPMDGQSX3WwiHsSvkdfGiFTKdoJwGkvqS3gny6XNPLtUtRwGERAs45639EfR5XfT", { "commitment": "finalized" } ]` }, { name: 'Slot Subscribe (slotSubscribe)', value: 'slotSubscribe', parentGroup: 'Subscribe', description: '(Subscription Websocket) Subscribe to receive notification anytime a slot is processed by the validator.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'slotSubscribe', params: [] } } ] as IETHOperation[] export const unsubscribeOperations = [ { name: 'Eth Unsubscribe (eth_unsubscribe)', value: 'eth_unsubscribe', parentGroup: 'Unsubscribe', description: 'Cancels an existing subscription so that no further events are sent.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: subsOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'eth_unsubscribe', params: [] } }, { name: 'Account Unsubscribe (accountUnsubscribe)', value: 'accountUnsubscribe', parentGroup: 'Unsubscribe', description: '(Subscription Websocket) Unsubscribe from account change notifications', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'accountUnsubscribe', params: [] } }, { name: 'Log Unsubscribe (logUnsubscribe)', value: 'logUnsubscribe', parentGroup: 'Unsubscribe', description: '(Subscription Websocket) Unsubscribe from transaction logging.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'logUnsubscribe', params: [] } }, { name: 'Program Unsubscribe (programUnsubscribe)', value: 'programUnsubscribe', parentGroup: 'Unsubscribe', description: '(Subscription Websocket) Unsubscribe from program-owned account change notifications.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'programUnsubscribe', params: [] } }, { name: 'Root Unsubscribe (rootUnsubscribe)', value: 'rootUnsubscribe', parentGroup: 'Unsubscribe', description: '(Subscription Websocket) Unsubscribe from root notifications.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'rootUnsubscribe', params: [] } }, { name: 'Signature Unsubscribe (signatureUnsubscribe)', value: 'signatureUnsubscribe', parentGroup: 'Unsubscribe', description: '(Subscription Websocket) Unsubscribe from signature confirmation notification.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'signatureUnsubscribe', params: [] } }, { name: 'Slot Unsubscribe (slotUnsubscribe)', value: 'slotUnsubscribe', parentGroup: 'Unsubscribe', description: '(Subscription Websocket) Unsubscribe from slot notifications.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: solanaOperationsNetworks }, method: 'POST', body: { jsonrpc: '2.0', id: 1, method: 'slotUnsubscribe', params: [] } } ] as IETHOperation[] ================================================ FILE: packages/components/nodes/QuickNode/supportedNetwork.ts ================================================ import { INodeOptionsValue } from '../../src' import { NETWORK, NETWORK_LABEL } from '../../src/ChainNetwork' export const QuickNodeSupportedNetworks = [ { label: NETWORK_LABEL.MAINNET, name: NETWORK.MAINNET }, { label: NETWORK_LABEL.GÖRLI, name: NETWORK.GÖRLI }, { label: NETWORK_LABEL.MATIC, name: NETWORK.MATIC }, { label: NETWORK_LABEL.MATIC_MUMBAI, name: NETWORK.MATIC_MUMBAI }, { label: NETWORK_LABEL.BSC, name: NETWORK.BSC }, { label: NETWORK_LABEL.BSC_TESTNET, name: NETWORK.BSC_TESTNET }, { label: NETWORK_LABEL.ARBITRUM, name: NETWORK.ARBITRUM }, { label: NETWORK_LABEL.ARBITRUM_GOERLI, name: NETWORK.ARBITRUM_GOERLI }, { label: NETWORK_LABEL.ARBITRUM_NOVA, name: NETWORK.ARBITRUM_NOVA }, { label: NETWORK_LABEL.AVALANCHE, name: NETWORK.AVALANCHE }, { label: NETWORK_LABEL.AVALANCHE_TESTNET, name: NETWORK.AVALANCHE_TESTNET }, { label: NETWORK_LABEL.OPTIMISM, name: NETWORK.OPTIMISM }, { label: NETWORK_LABEL.OPTIMISM_GOERLI, name: NETWORK.OPTIMISM_GOERLI }, { label: NETWORK_LABEL.FANTOM, name: NETWORK.FANTOM }, { label: NETWORK_LABEL.CELO, name: NETWORK.CELO }, { label: NETWORK_LABEL.GNOSIS, name: NETWORK.GNOSIS }, { label: NETWORK_LABEL.SOLANA, name: NETWORK.SOLANA }, { label: NETWORK_LABEL.SOLANA_DEVNET, name: NETWORK.SOLANA_DEVNET }, { label: NETWORK_LABEL.SOLANA_TESTNET, name: NETWORK.SOLANA_TESTNET } ] as INodeOptionsValue[] //REMAINING: ALGORAND, HARMONY, STACKS ================================================ FILE: packages/components/nodes/RequestFinance/RequestFinance.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' import moment from 'moment' import { invoiceParameters, salaryParameters } from './constants' interface IInvoiceItems { name: string unitPrice: number quantity: number taxType?: string taxAmount?: number } class RequestFinance implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Request Finance' this.name = 'requestFinance' this.icon = 'requestFinance.svg' this.type = 'action' this.category = 'Accounting' this.version = 1.0 this.description = 'Issue invoices and accept payments in cryptocurrency using RequestFinance API' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Operation', name: 'operation', type: 'options', options: [ { label: 'Get Invoice', name: 'getSingleInvoice', description: 'Returns single invoice from RequestFinance account.' }, { label: 'Create Invoice', name: 'createInvoice', description: 'Creates a new payable invoice.' }, { label: 'Create Salary Payment', name: 'createSalaryPayment', description: 'Creates a new payable salary payment.' } ] } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'RequestFinance API Key', name: 'requestFinanceApi' } ], default: 'requestFinanceApi' } ] as INodeParams[] this.inputParameters = [ { label: 'Invoice', name: 'invoiceId', type: 'asyncOptions', loadMethod: 'getInvoices', show: { 'actions.operation': ['getSingleInvoice'] } }, ...salaryParameters, ...invoiceParameters ] as INodeParams[] } loadMethods = { async getInvoiceCurrency(): Promise { const returnData: INodeOptionsValue[] = [] try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://api.request.finance/currency/list/invoicing` } const response = await axios(axiosConfig) const tokens = response.data const tokenSet: Set = new Set() for (let i = 0; i < tokens.length; i += 1) { const token = tokens[i] if (!token.symbol.includes('-')) tokenSet.add(`${token.symbol} | ${token.decimals}`) } tokenSet.forEach((tkn) => { const data = { label: tkn.split('|')[0].trim(), name: tkn } as INodeOptionsValue returnData.push(data) }) return returnData } catch (e) { return returnData } }, async getPaymentCurrency(): Promise { const returnData: INodeOptionsValue[] = [] try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://api.request.finance/currency/list/invoicing` } const response = await axios(axiosConfig) const tokens = response.data const tokenSet: Set = new Set() for (let i = 0; i < tokens.length; i += 1) { const token = tokens[i] if (token.id.includes('-')) tokenSet.add(`${token.id} | ${token.decimals}`) } tokenSet.forEach((tkn) => { const data = { label: tkn.split('|')[0].trim(), name: tkn } as INodeOptionsValue returnData.push(data) }) return returnData } catch (e) { return returnData } }, async getCountry(): Promise { const returnData: INodeOptionsValue[] = [] try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://restcountries.com/v2/all` } const response = await axios(axiosConfig) const countries = response.data for (let i = 0; i < countries.length; i += 1) { const country = countries[i] const data = { label: country.name, name: country.alpha2Code } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } }, async getClients(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const credentials = nodeData.credentials if (credentials === undefined) { return returnData } const apiKey = credentials!.apiKey as string try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://api.request.finance/clients?type=customer`, headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: apiKey } } const response = await axios(axiosConfig) const clients = response.data for (let i = 0; i < clients.length; i += 1) { const client = clients[i] const data = { label: client.email, name: JSON.stringify(client) } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } }, async getEmployees(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const credentials = nodeData.credentials if (credentials === undefined) { return returnData } const apiKey = credentials!.apiKey as string try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://api.request.finance/clients?type=employee`, headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: apiKey } } const response = await axios(axiosConfig) const employees = response.data for (let i = 0; i < employees.length; i += 1) { const employee = employees[i] const data = { label: employee.email, name: JSON.stringify(employee) } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } }, async getInvoices(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const credentials = nodeData.credentials if (credentials === undefined) { return returnData } const apiKey = credentials!.apiKey as string try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://api.request.finance/invoices?filterBy=sent&variant=rnf_invoice`, headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: apiKey } } const response = await axios(axiosConfig) const invoices = response.data for (let i = 0; i < invoices.length; i += 1) { const invoice = invoices[i] const data = { label: `${invoice.invoiceNumber} (${moment(invoice.creationDate).format('MMMM D, YYYY')})`, name: invoice.id } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } } } async run(nodeData: INodeData): Promise { const actionsData = nodeData.actions const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (actionsData === undefined || inputParametersData === undefined) { throw new Error('Required data missing!') } if (credentials === undefined) { throw new Error('Missing credentials!') } // Get Operation const operation = actionsData.operation as string // Get API key const apiKey = credentials.apiKey as string // Get Input Params const invoiceItems = inputParametersData.invoiceItems as unknown as IInvoiceItems[] const invoiceCurrencyValue = inputParametersData.invoiceCurrency as string const invoiceCurrency = invoiceCurrencyValue ? invoiceCurrencyValue.split('|')[0].trim() : '' const invoiceCurrencyDecimals = invoiceCurrencyValue ? invoiceCurrencyValue.split('|')[1].trim() : '' const paymentAddress = inputParametersData.paymentAddress as string const paymentCurrencyValue = inputParametersData.paymentCurrency as string const paymentCurrency = paymentCurrencyValue ? paymentCurrencyValue.split('|')[0].trim() : '' const paymentDueDate = inputParametersData.paymentDueDate as string const clientType = inputParametersData.clientType as string const existingClient = inputParametersData.existingClient as string const buyerInfoEmail = inputParametersData.buyerInfoEmail as string const buyerInfoBusinessName = inputParametersData.buyerInfoBusinessName as string const buyerInfoFirstName = inputParametersData.buyerInfoFirstName as string const buyerInfoLastName = inputParametersData.buyerInfoLastName as string const buyerInfoStreetAddress = inputParametersData.buyerInfoStreetAddress as string const buyerInfoExtendedAddress = inputParametersData.buyerInfoExtendedAddress as string const buyerInfoPostalCode = inputParametersData.buyerInfoPostalCode as string const buyerInfoRegion = inputParametersData.buyerInfoRegion as string const buyerInfoCountry = inputParametersData.buyerInfoCountry as string const buyerInfoTaxRegistration = inputParametersData.buyerInfoTaxRegistration as string const invoiceId = inputParametersData.invoiceId as string const salaryCurrencyValue = inputParametersData.salaryCurrency as string const salaryCurrency = salaryCurrencyValue ? salaryCurrencyValue.split('|')[0].trim() : '' const salaryCurrencyDecimals = salaryCurrencyValue ? salaryCurrencyValue.split('|')[1].trim() : '' const salaryAmount = inputParametersData.salaryAmount as string const salaryPaymentCurrencyValue = inputParametersData.salaryPaymentCurrency as string const salaryPaymentCurrency = salaryPaymentCurrencyValue ? salaryPaymentCurrencyValue.split('|')[0].trim() : '' const employeeType = inputParametersData.employeeType as string const existingEmployee = inputParametersData.existingEmployee as string const employeeEmail = inputParametersData.employeeEmail as string const employeePaymentAddress = inputParametersData.employeePaymentAddress as string const employeeFirstName = inputParametersData.employeeFirstName as string const employeeLastName = inputParametersData.employeeLastName as string const companyEmail = inputParametersData.companyEmail as string const companyBusinessName = inputParametersData.companyBusinessName as string const companyFirstName = inputParametersData.companyFirstName as string const companyLastName = inputParametersData.companyLastName as string const companyStreetAddress = inputParametersData.companyStreetAddress as string const companyExtendedAddress = inputParametersData.companyExtendedAddress as string const companyPostalCode = inputParametersData.companyPostalCode as string const companyRegion = inputParametersData.companyRegion as string const companyCountry = inputParametersData.companyCountry as string const returnData: ICommonObject[] = [] let responseData: any let url = '' let method: Method = 'GET' let body: ICommonObject = {} if (operation === 'createInvoice') { url = 'https://api.request.finance/invoices' method = 'POST' const invoicesSent = await getNumberOfInvoiceSent(apiKey) body.invoiceNumber = (invoicesSent + 1).toString() //invoiceItems const invoiceItemsArray: any[] = [] for (let j = 0; j < invoiceItems.length; j += 1) { const invoiceItem: ICommonObject = { currency: invoiceCurrency, name: invoiceItems[j].name, quantity: Number(invoiceItems[j].quantity), unitPrice: formatUnitPrice(invoiceItems[j].unitPrice, Number(invoiceCurrencyDecimals)) } if (invoiceItems[j].taxType && invoiceItems[j].taxAmount) { invoiceItem.tax = { type: invoiceItems[j].taxType, amount: invoiceItems[j].taxAmount } } invoiceItemsArray.push(invoiceItem) } body.invoiceItems = invoiceItemsArray //payment if (paymentDueDate) { const dueDate = { dueDate: paymentDueDate } body.paymentTerms = dueDate } body.paymentAddress = paymentAddress body.paymentCurrency = paymentCurrency //buyer const buyerInfo: ICommonObject = {} if (clientType === 'existing') { const client = JSON.parse(existingClient) buyerInfo.email = client.email if (client.firstName) buyerInfo.firstName = client.firstName if (client.lastName) buyerInfo.lastName = client.lastName if (client.businessName) buyerInfo.businessName = client.businessName if (client.taxRegistration) buyerInfo.taxRegistration = client.taxRegistration const buyerAddress = { streetAddress: client.address.streetAddress, extendedAddress: client.address.extendedAddress, postalCode: client.address.postalCode, region: client.address.region, city: client.address.city, country: client.address.country } buyerInfo.address = buyerAddress } else { buyerInfo.email = buyerInfoEmail if (buyerInfoFirstName) buyerInfo.firstName = buyerInfoFirstName if (buyerInfoLastName) buyerInfo.lastName = buyerInfoLastName if (buyerInfoBusinessName) buyerInfo.businessName = buyerInfoBusinessName if (buyerInfoTaxRegistration) buyerInfo.taxRegistration = buyerInfoTaxRegistration const buyerAddress = { streetAddress: buyerInfoStreetAddress || '', extendedAddress: buyerInfoExtendedAddress || '', postalCode: buyerInfoPostalCode || '', region: buyerInfoRegion || '', city: buyerInfoRegion || '', country: buyerInfoCountry || '' } buyerInfo.address = buyerAddress } body.buyerInfo = buyerInfo } if (operation === 'getSingleInvoice') { url = `https://api.request.finance/invoices/${invoiceId}` method = 'GET' } if (operation === 'createSalaryPayment') { url = 'https://api.request.finance/invoices' method = 'POST' body.meta = { format: 'rnf_salary', version: '0.0.3' } const salarySent = await getNumberOfSalarySent(apiKey) body.invoiceNumber = (salarySent + 1).toString() //invoiceItems const invoiceItem: ICommonObject = { currency: salaryCurrency, name: 'Salary', quantity: 1, unitPrice: formatUnitPrice(salaryAmount, Number(salaryCurrencyDecimals)) } body.invoiceItems = [invoiceItem] //payment const dueDate = { dueDate: moment().endOf('day').toString() } body.paymentTerms = dueDate body.paymentCurrency = salaryPaymentCurrency //seller const sellerInfo: ICommonObject = {} if (employeeType === 'existing') { const employee = JSON.parse(existingEmployee) sellerInfo.email = employee.email body.paymentAddress = employee.salaryPaymentAddress if (employee.firstName) sellerInfo.firstName = employee.firstName if (employee.lastName) sellerInfo.lastName = employee.lastName } else { sellerInfo.email = employeeEmail body.paymentAddress = employeePaymentAddress if (employeeFirstName) sellerInfo.firstName = employeeFirstName if (employeeLastName) sellerInfo.lastName = employeeLastName } body.sellerInfo = sellerInfo //buyer const buyerInfo: ICommonObject = {} buyerInfo.email = companyEmail if (companyFirstName) buyerInfo.firstName = companyFirstName if (companyLastName) buyerInfo.lastName = companyLastName if (companyBusinessName) buyerInfo.businessName = companyBusinessName const buyerAddress = { streetAddress: companyStreetAddress || '', extendedAddress: companyExtendedAddress || '', postalCode: companyPostalCode || '', region: companyRegion || '', city: companyRegion || '', country: companyCountry || '' } buyerInfo.address = buyerAddress body.buyerInfo = buyerInfo } try { const axiosConfig: AxiosRequestConfig = { method, url, headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: apiKey } } if (Object.keys(body).length) axiosConfig.data = body const response = await axios(axiosConfig) responseData = response.data // Convert to on-chain request if (operation === 'createInvoice' || operation === 'createSalaryPayment') { const invoiceId = responseData.id const axiosConfig2: AxiosRequestConfig = { method, url: `https://api.request.finance/invoices/${invoiceId}`, data: body, headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: apiKey } } await axios(axiosConfig2) } } catch (error) { console.error(error.response.data.errors) throw handleErrorMessage(error) } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } const getNumberOfInvoiceSent = async (apiKey: string) => { const axiosConfig: AxiosRequestConfig = { method: 'GET', url: 'https://api.request.finance/invoices?filterBy=sent&variant=rnf_invoice', headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: apiKey } } const response = await axios(axiosConfig) return response.data.length || 0 } const getNumberOfSalarySent = async (apiKey: string) => { const axiosConfig: AxiosRequestConfig = { method: 'GET', url: 'https://api.request.finance/invoices?variant=rnf_salary', headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: apiKey } } const response = await axios(axiosConfig) return response.data.length || 0 } const addTrailingZeros = (num: number, totalLength: number) => { return Number(String(num).padEnd(totalLength + 1, '0')) } const formatUnitPrice = (x: number | string, decimals: number) => { return (Number(x) * addTrailingZeros(1, decimals)).toFixed(0) } module.exports = { nodeClass: RequestFinance } ================================================ FILE: packages/components/nodes/RequestFinance/RequestFinanceTrigger.ts ================================================ import { CronJob } from 'cron' import { ICronJobs, INode, INodeData, INodeOptionsValue, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import EventEmitter from 'events' import axios, { AxiosRequestConfig, AxiosRequestHeaders, Method } from 'axios' import moment from 'moment' class RequestFinanceTrigger extends EventEmitter implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] cronJobs: ICronJobs constructor() { super() this.label = 'Request Finance Trigger' this.name = 'requestFinanceTrigger' this.icon = 'requestFinance.svg' this.type = 'trigger' this.category = 'Accounting' this.version = 1.0 this.description = 'Start workflow whenever receive new invoice or invoice status has changed' this.incoming = 0 this.outgoing = 1 this.cronJobs = {} this.actions = [ { label: 'Invoice Status', name: 'invoiceStatus', type: 'options', options: [ { label: 'New', name: 'new', description: 'Trigger workflow when new invoice received' }, { label: 'Accepted', name: 'accepted', description: 'The invoice has been approved by the buyer.' }, { label: 'Declared Paid', name: 'declaredPaid', description: 'The buyer declared the invoice as paid. The seller has to confirm before the invoice can move into the paid status. This is necessary for currencies, where the Request Network does not yet support payment detection.' }, { label: 'Paid', name: 'paid', description: 'Seller has confirmed and marked the invoice as paid, i.e: the buyer paid the invoice.' }, { label: 'Canceled', name: 'canceled', description: 'The seller canceled the invoice.' }, { label: 'Rejected', name: 'rejected', description: 'The buyer rejected the invoice.' } ], default: '', description: 'Status of an invoice' } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'RequestFinance API Key', name: 'requestFinanceApi' } ], default: 'requestFinanceApi' } ] as INodeParams[] this.inputParameters = [ { label: 'Select Invoice', name: 'invoiceType', type: 'options', options: [ { label: 'Retrieve from existing invoices', name: 'existingInvoice' }, { label: 'Custom invoice ID', name: 'customInvoice' } ], hide: { 'actions.invoiceStatus': ['new'] } }, { label: 'Invoice ID', name: 'invoiceId', type: 'string', placeholder: '63e2c662d24445b79ada9c3d', hide: { 'actions.invoiceStatus': ['new'] }, show: { 'inputParameters.invoiceType': ['customInvoice'] } }, { label: 'Invoice', name: 'invoiceId', type: 'asyncOptions', loadMethod: 'getInvoices', hide: { 'actions.invoiceStatus': ['new'] }, show: { 'inputParameters.invoiceType': ['existingInvoice'] } }, { label: 'Polling Time', name: 'pollTime', type: 'options', description: 'How often should we keep checking the invoice status', options: [ { label: 'Every 15 secs', name: '15s' }, { label: 'Every 30 secs', name: '30s' }, { label: 'Every 1 min', name: '1min' }, { label: 'Every 5 min', name: '5min' }, { label: 'Every 10 min', name: '10min' } ], default: '30s' } ] as INodeParams[] } loadMethods = { async getInvoices(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const credentials = nodeData.credentials if (credentials === undefined) { return returnData } const apiKey = credentials!.apiKey as string try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://api.request.finance/invoices?variant=rnf_invoice`, headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: apiKey } } const response = await axios(axiosConfig) const invoices = response.data for (let i = 0; i < invoices.length; i += 1) { const invoice = invoices[i] const data = { label: `#${invoice.invoiceNumber} (${moment(invoice.creationDate).format('MMMM D, YYYY')})`, name: invoice.id } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } } } async runTrigger(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters const actionData = nodeData.actions const credentials = nodeData.credentials if (actionData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } if (credentials === undefined) { throw new Error('Missing credentials!') } // Get API key const apiKey = credentials.apiKey as string // Get Invoice const invoiceId = inputParametersData.invoiceId as string const invoiceStatus = actionData.invoiceStatus as string let url = '' const method: Method = 'GET' const headers: AxiosRequestHeaders = { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: apiKey } const emitEventKey = nodeData.emitEventKey as string const pollTime = (inputParametersData?.pollTime as string) || '30s' const cronTimes: string[] = [] if (pollTime === '15s') { cronTimes.push(`*/15 * * * * *`) } else if (pollTime === '30s') { cronTimes.push(`*/30 * * * * *`) } else if (pollTime === '1min') { cronTimes.push(`*/1 * * * *`) } else if (pollTime === '5min') { cronTimes.push(`*/5 * * * *`) } else if (pollTime === '10min') { cronTimes.push(`*/10 * * * *`) } async function getInvoiceStatusAPI() { url = `https://api.request.finance/invoices/${invoiceId}` const axiosConfig: AxiosRequestConfig = { method, url, headers } return await axios(axiosConfig) } async function getReceivedInvoices() { url = `https://api.request.finance/invoices?filterBy=received&variant=rnf_invoice&status=open` const axiosConfig: AxiosRequestConfig = { method, url, headers } return await axios(axiosConfig) } let lastInvoicesAmount = 0 try { // Get initial data if (invoiceStatus === 'new') { const response = await getReceivedInvoices() const invoices = response.data || [] lastInvoicesAmount = invoices.length } // Trigger when cron job hits const executeTrigger = async () => { if (invoiceStatus === 'new') { const newResponse = await getReceivedInvoices() const newResponseData = newResponse.data || [] const newInvoicesAmount = newResponseData.length if (newInvoicesAmount > lastInvoicesAmount) { const differenceAmount = newInvoicesAmount - lastInvoicesAmount const returnData = [] for (let i = 0; i < differenceAmount; i += 1) { returnData.push(newResponseData[i]) } lastInvoicesAmount = newInvoicesAmount this.emit(emitEventKey, returnNodeExecutionData(returnData)) } } else { const newResponse = await getInvoiceStatusAPI() const newResponseData = newResponse.data if (newResponseData.status === invoiceStatus) { this.emit(emitEventKey, returnNodeExecutionData(newResponseData)) } } } // Start the cron-jobs if (Object.prototype.hasOwnProperty.call(this.cronJobs, emitEventKey)) { for (const cronTime of cronTimes) { // Automatically start the cron job this.cronJobs[emitEventKey].push(new CronJob(cronTime, executeTrigger, undefined, true)) } } else { for (const cronTime of cronTimes) { // Automatically start the cron job this.cronJobs[emitEventKey] = [new CronJob(cronTime, executeTrigger, undefined, true)] } } } catch (e) { throw handleErrorMessage(e) } } async removeTrigger(nodeData: INodeData): Promise { const emitEventKey = nodeData.emitEventKey as string if (Object.prototype.hasOwnProperty.call(this.cronJobs, emitEventKey)) { const cronJobs = this.cronJobs[emitEventKey] for (const cronJob of cronJobs) { cronJob.stop() } this.removeAllListeners(emitEventKey) } } } module.exports = { nodeClass: RequestFinanceTrigger } ================================================ FILE: packages/components/nodes/RequestFinance/constants.ts ================================================ import { INodeParams } from '../../src' export const salaryParameters = [ { label: 'Salary Currency', name: 'salaryCurrency', description: 'Currency in which the salary will be issued in.', type: 'asyncOptions', loadMethod: 'getInvoiceCurrency', show: { 'actions.operation': ['createSalaryPayment'] } }, { label: 'Salary Amount', name: 'salaryAmount', description: 'Amount of salary to be paid', type: 'number', placeholder: '1000', show: { 'actions.operation': ['createSalaryPayment'] } }, { label: 'How do you want to pay?', name: 'salaryPaymentCurrency', type: 'asyncOptions', loadMethod: 'getPaymentCurrency', description: 'Currency in which the employer would like to pay in.', show: { 'actions.operation': ['createSalaryPayment'] } }, { label: 'Employee Type', name: 'employeeType', type: 'options', options: [ { label: 'Existing', name: 'existing' }, { label: 'New', name: 'new' } ], show: { 'actions.operation': ['createSalaryPayment'] } }, { label: 'Employee', name: 'existingEmployee', type: 'asyncOptions', loadMethod: 'getEmployees', show: { 'actions.operation': ['createSalaryPayment'], 'inputParameters.employeeType': ['existing'] } }, { label: 'Employee Email', name: 'employeeEmail', type: 'string', description: 'Email of the employee', default: '', show: { 'actions.operation': ['createSalaryPayment'], 'inputParameters.employeeType': ['new'] } }, { label: 'Employee Wallet Address', name: 'employeePaymentAddress', type: 'string', description: 'Wallet address of the employee to receive salary', default: '', show: { 'actions.operation': ['createSalaryPayment'], 'inputParameters.employeeType': ['new'] } }, { label: 'Employee First Name', name: 'employeeFirstName', type: 'string', default: '', optional: true, show: { 'actions.operation': ['createSalaryPayment'], 'inputParameters.employeeType': ['new'] } }, { label: 'Employee Last Name', name: 'employeeLastName', type: 'string', default: '', optional: true, show: { 'actions.operation': ['createSalaryPayment'], 'inputParameters.employeeType': ['new'] } }, { label: 'Owner Email', name: 'companyEmail', type: 'string', description: 'Email of the owner that issues salary payment. Refer to
    Account page', default: '', show: { 'actions.operation': ['createSalaryPayment'] } }, { label: 'Owner First Name', name: 'companyFirstName', description: 'First name of the owner that issues salary payment. Refer to Account page', type: 'string', default: '', show: { 'actions.operation': ['createSalaryPayment'] } }, { label: 'Company Last Name', name: 'companyLastName', description: 'Last name of the owner that issues salary payment. Refer to Account page', type: 'string', default: '', show: { 'actions.operation': ['createSalaryPayment'] } }, { label: 'Company Business Name', name: 'companyBusinessName', type: 'string', description: 'Business name of the company. Refer to Account page', optional: true, default: '', show: { 'actions.operation': ['createSalaryPayment'] } }, { label: 'Company Address Line 1', name: 'companyStreetAddress', type: 'string', description: 'Street, house, apartment of the company.', default: '', optional: true, show: { 'actions.operation': ['createSalaryPayment'] } }, { label: 'Company Address Line 2', name: 'companyExtendedAddress', type: 'string', description: 'Street, house, apartment of the company.', default: '', optional: true, show: { 'actions.operation': ['createSalaryPayment'] } }, { label: 'Company Postal Code', name: 'companyPostalCode', type: 'string', description: 'Post code of the company.', default: '', optional: true, show: { 'actions.operation': ['createSalaryPayment'] } }, { label: 'Company State', name: 'companyRegion', type: 'string', description: 'Region of the company (e.g. “California”).', default: '', optional: true, show: { 'actions.operation': ['createSalaryPayment'] } }, { label: 'Company Country', name: 'companyCountry', description: 'Country of the company (e.g. US).', type: 'asyncOptions', loadMethod: 'getCountry', optional: true, show: { 'actions.operation': ['createSalaryPayment'] } } ] as INodeParams[] export const invoiceParameters = [ { label: 'Invoice Currency', name: 'invoiceCurrency', description: 'Currency in which the invoice is denominated. For example, invoices can be denominated in USD, but buyers can pay in crypto.', type: 'asyncOptions', loadMethod: 'getInvoiceCurrency', show: { 'actions.operation': ['createInvoice'] } }, { label: 'Invoice Items', name: 'invoiceItems', type: 'array', array: [ { label: 'Name', name: 'name', type: 'string', description: 'Name of the product/service for which the invoice is created.', default: '' }, { label: 'Unit Price', name: 'unitPrice', description: 'Price of the product/service, excl. taxes. Max 2 decimal places', type: 'number' }, { label: 'Quantity', name: 'quantity', description: 'Quantity of the product/service that was provided.', type: 'number' }, { label: 'Tax Type', name: 'taxType', description: 'Currency code in which the invoice is denominated. For example, invoices can be denominated in USD, but buyers can pay in crypto.', type: 'options', options: [ { label: 'Fixed', name: 'fixed' }, { label: 'Percentage', name: 'percentage' } ], optional: true }, { label: 'Tax Amount', name: 'taxAmount', description: 'Amount of the tax.', type: 'number', show: { 'inputParameters.invoiceItems[$index].taxType': ['fixed', 'percentage'] } } ], show: { 'actions.operation': ['createInvoice'] } }, { label: 'Payment Address', name: 'paymentAddress', type: 'string', description: 'Address which will receive the payment.', default: '', placeholder: '0xE597E474889537A3A9120d1B5793cdFAEf6B6c10', show: { 'actions.operation': ['createInvoice'] } }, { label: 'Payment Currency', name: 'paymentCurrency', description: 'Currency in which the invoice can be paid.', type: 'asyncOptions', loadMethod: 'getPaymentCurrency', show: { 'actions.operation': ['createInvoice'] } }, { label: 'Payment Due Date', name: 'paymentDueDate', description: 'Due date of the invoice. Last date the buyer can pay.', type: 'date', optional: true, show: { 'actions.operation': ['createInvoice'] } }, { label: 'Client Type', name: 'clientType', type: 'options', options: [ { label: 'Existing', name: 'existing' }, { label: 'New', name: 'new' } ], show: { 'actions.operation': ['createInvoice'] } }, { label: 'Client', name: 'existingClient', type: 'asyncOptions', loadMethod: 'getClients', show: { 'actions.operation': ['createInvoice'], 'inputParameters.clientType': ['existing'] } }, { label: 'Client Email', name: 'buyerInfoEmail', type: 'string', description: 'Email of the buyer/customer/client', default: '', show: { 'actions.operation': ['createInvoice'], 'inputParameters.clientType': ['new'] } }, { label: 'Client Business Name', name: 'buyerInfoBusinessName', type: 'string', description: 'Business name of the buyer/customer/client', default: '', optional: true, show: { 'actions.operation': ['createInvoice'], 'inputParameters.clientType': ['new'] } }, { label: 'Client First Name', name: 'buyerInfoFirstName', type: 'string', default: '', optional: true, show: { 'actions.operation': ['createInvoice'], 'inputParameters.clientType': ['new'] } }, { label: 'Client Last Name', name: 'buyerInfoLastName', type: 'string', default: '', optional: true, show: { 'actions.operation': ['createInvoice'], 'inputParameters.clientType': ['new'] } }, { label: 'Client Address Line 1', name: 'buyerInfoStreetAddress', type: 'string', description: 'Street, house, apartment of the buyer.', default: '', optional: true, show: { 'actions.operation': ['createInvoice'], 'inputParameters.clientType': ['new'] } }, { label: 'Client Address Line 2', name: 'buyerInfoExtendedAddress', type: 'string', description: 'Street, house, apartment of the buyer.', default: '', optional: true, show: { 'actions.operation': ['createInvoice'], 'inputParameters.clientType': ['new'] } }, { label: 'Client Postal Code', name: 'buyerInfoPostalCode', type: 'string', description: 'Post code of the buyer.', default: '', optional: true, show: { 'actions.operation': ['createInvoice'], 'inputParameters.clientType': ['new'] } }, { label: 'Client State', name: 'buyerInfoRegion', type: 'string', description: 'Region of the buyer (e.g. “California”).', default: '', optional: true, show: { 'actions.operation': ['createInvoice'], 'inputParameters.clientType': ['new'] } }, { label: 'Client Country', name: 'buyerInfoCountry', description: 'Country of the buyer (e.g. US).', type: 'asyncOptions', loadMethod: 'getCountry', optional: true, show: { 'actions.operation': ['createInvoice'], 'inputParameters.clientType': ['new'] } }, { label: 'Client Tax Registration', name: 'buyerInfoTaxRegistration', type: 'string', description: 'Tax registration number of the buyer.', default: '', optional: true, show: { 'actions.operation': ['createInvoice'], 'inputParameters.clientType': ['new'] } } ] as INodeParams[] ================================================ FILE: packages/components/nodes/Scheduler/Scheduler.ts ================================================ import { ICommonObject, ICronJobs, INode, INodeData, INodeParams, NodeType } from '../../src/Interface' import { CronJob } from 'cron' import { returnNodeExecutionData } from '../../src/utils' import EventEmitter from 'events' interface IScheduleTimes { mode: string timezone: string hour?: number minute?: number dayOfMonth?: number weekday: string value?: number unit?: string specificDateTime?: string } class Scheduler extends EventEmitter implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number inputParameters?: INodeParams[] cronJobs: ICronJobs constructor() { super() this.label = 'Scheduler' this.name = 'scheduler' this.icon = 'scheduler.svg' this.type = 'trigger' this.category = 'Utilities' this.version = 1.1 this.description = 'Start workflow at scheduled times' this.incoming = 0 this.outgoing = 1 this.cronJobs = {} this.inputParameters = [ { label: 'Pattern', name: 'pattern', type: 'options', options: [ { label: 'Repetitive', name: 'repetitive', description: 'Workflow will be triggered repetitively every X' }, { label: 'Once', name: 'once', description: 'Workflow will be triggered only once at specific time' } ], default: 'repetitive' }, { label: 'Date Time', name: 'specificDateTime', type: 'date', description: 'Choose a specific date time to trigger the workflow once', show: { 'inputParameters.pattern': ['once'] } }, { label: 'Schedules', name: 'scheduleTimes', type: 'array', show: { 'inputParameters.pattern': ['repetitive'] }, array: [ { label: 'Mode', name: 'mode', type: 'options', options: [ { label: 'Every Day', name: 'everyDay' }, { label: 'Every Week', name: 'everyWeek' }, { label: 'Every Month', name: 'everyMonth' }, { label: 'Every X', name: 'everyX' }, { label: 'Every Specific Time', name: 'specific' } ], default: 'everyDay' }, { label: 'Specific Date Time', name: 'specificDateTime', type: 'date', description: 'Choose a specific date time to trigger the workflow', show: { 'inputParameters.scheduleTimes[$index].mode': ['specific'] } }, { label: 'Hour', name: 'hour', type: 'number', hide: { 'inputParameters.scheduleTimes[$index].mode': ['everyX', 'specific'] }, default: new Date().getHours(), description: '[24H Format] Scheduled hour to trigger workflow' }, { label: 'Minute', name: 'minute', type: 'number', hide: { 'inputParameters.scheduleTimes[$index].mode': ['everyX', 'specific'] }, default: new Date().getMinutes(), description: '[0 - 59] Scheduled minute to trigger workflow' }, { label: 'Day of Month', name: 'dayOfMonth', type: 'number', show: { 'inputParameters.scheduleTimes[$index].mode': ['everyMonth'] }, default: new Date().getDate(), description: '[1 - 31] Scheduled day to trigger workflow' }, { label: 'Weekday', name: 'weekday', type: 'options', show: { 'inputParameters.scheduleTimes[$index].mode': ['everyWeek'] }, options: [ { label: 'Monday', name: '1' }, { label: 'Tuesday', name: '2' }, { label: 'Wednesday', name: '3' }, { label: 'Thursday', name: '4' }, { label: 'Friday', name: '5' }, { label: 'Saturday', name: '6' }, { label: 'Sunday', name: '0' } ], default: new Date().getDay().toString(), description: 'Scheduled weekday to trigger workflow' }, { label: 'Value', name: 'value', type: 'number', show: { 'inputParameters.scheduleTimes[$index].mode': ['everyX'] }, default: 1, description: 'Scheduled X seconds/minutes/hours to trigger workflow' }, { label: 'Unit', name: 'unit', type: 'options', show: { 'inputParameters.scheduleTimes[$index].mode': ['everyX'] }, options: [ { label: 'Seconds', name: 'seconds' }, { label: 'Minutes', name: 'minutes' }, { label: 'Hours', name: 'hours' } ], default: 'hours', description: 'Units of scheduled X seconds / minutes / hours' } ] } ] } async runTrigger(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters if (inputParametersData === undefined) { throw new Error('Required data missing') } const pattern = inputParametersData.pattern as string const scheduleTimes = inputParametersData.scheduleTimes as unknown as IScheduleTimes[] const cronTimes: string[] = [] if (pattern === 'once') { const specificDateTime = inputParametersData.specificDateTime as string cronTimes.push(dateToCron(new Date(specificDateTime))) } else { for (const scheduleItem of scheduleTimes) { if (scheduleItem.mode === 'everyX') { if (scheduleItem.unit === 'seconds') { // Every X seconds cronTimes.push(`*/${scheduleItem.value} * * * * *`) } else if (scheduleItem.unit === 'minutes') { // Every X minutes cronTimes.push(`*/${scheduleItem.value} * * * *`) } else if (scheduleItem.unit === 'hours') { // At 0 minutes past the hour, every X hours cronTimes.push(`0 */${scheduleItem.value} * * *`) } } if (scheduleItem.mode === 'everyDay') { const minute = scheduleItem.minute || '0' const hour = scheduleItem.hour || '0' // At XX:XX AM/PM, every days cronTimes.push(`${minute} ${hour} * * *`) } if (scheduleItem.mode === 'everyWeek') { const minute = scheduleItem.minute || '0' const hour = scheduleItem.hour || '0' const weekday = scheduleItem.weekday || '0' // At XX:XX AM/PM, only on Monday/Tuesday... cronTimes.push(`${minute} ${hour} * * ${weekday}`) } if (scheduleItem.mode === 'everyMonth') { const minute = scheduleItem.minute || '0' const hour = scheduleItem.hour || '0' const dayOfMonth = scheduleItem.dayOfMonth || '0' // At XX:XX AM/PM, on day X of the month cronTimes.push(`${minute} ${hour} ${dayOfMonth} * *`) } if (scheduleItem.mode === 'specific') { const specificDateTime = scheduleItem.specificDateTime as string cronTimes.push(dateToCron(new Date(specificDateTime))) } } } const emitEventKey = nodeData.emitEventKey as string // The function to fire at the specified time const onTick = () => { const returnData: ICommonObject[] = [] returnData.push({ date: new Date().toDateString(), time: new Date().toTimeString(), cron: 'SUCCESS' }) this.emit(emitEventKey, returnNodeExecutionData(returnData)) if (pattern === 'once') this.removeTrigger(nodeData) } // Start the cron-jobs if (Object.prototype.hasOwnProperty.call(this.cronJobs, emitEventKey)) { for (const cronTime of cronTimes) { // Automatically start the cron job this.cronJobs[emitEventKey].push(new CronJob(cronTime, onTick, undefined, true)) } } else { for (const cronTime of cronTimes) { // Automatically start the cron job this.cronJobs[emitEventKey] = [new CronJob(cronTime, onTick, undefined, true)] } } } async removeTrigger(nodeData: INodeData): Promise { const emitEventKey = nodeData.emitEventKey as string if (Object.prototype.hasOwnProperty.call(this.cronJobs, emitEventKey)) { const cronJobs = this.cronJobs[emitEventKey] for (const cronJob of cronJobs) { cronJob.stop() } this.removeAllListeners(emitEventKey) } } } const dateToCron = (date: Date) => { const minutes = date.getMinutes() const hours = date.getHours() const days = date.getDate() const months = date.getMonth() const dayOfWeek = date.getDay() return `${minutes} ${hours} ${days} ${months} ${dayOfWeek}` } module.exports = { nodeClass: Scheduler } ================================================ FILE: packages/components/nodes/Slack/Slack.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' interface ISlackWebhook { text?: string blocks?: any[] } class Slack implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number inputParameters?: INodeParams[] constructor() { this.label = 'Slack' this.name = 'slack' this.icon = 'slack.svg' this.type = 'action' this.category = 'Communication' this.version = 1.0 this.description = 'Post message in Slack channel' this.incoming = 1 this.outgoing = 1 this.inputParameters = [ { label: 'Webhook URL', name: 'webhookUrl', type: 'string', default: '', description: 'Webhook URL for the channel. Learn more: https://api.slack.com/messaging/webhooks' }, { label: 'Message', description: 'Message contents', name: 'text', type: 'string', default: '' } ] as INodeParams[] } async run(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters if (inputParametersData === undefined) { throw new Error('Required data missing') } const returnData: ICommonObject[] = [] const body: ISlackWebhook = {} const webhookUrl = inputParametersData.webhookUrl as string const text = inputParametersData.text as string body.text = text let responseData: any let maxRetries = 5 do { try { const axiosConfig: AxiosRequestConfig = { method: 'POST' as Method, url: `${webhookUrl}`, data: body, headers: { 'Content-Type': 'application/json; charset=utf-8' } } const response = await axios(axiosConfig) responseData = response.data break } catch (error) { // Rate limit exceeded if (error.response && error.response.status === 429) { const retryAfter = error.response?.headers['retry-after'] || 60 await new Promise((resolve, _) => { setTimeout(() => { resolve() }, retryAfter * 1000) }) continue } throw handleErrorMessage(error) } } while (--maxRetries) if (maxRetries <= 0) { throw new Error('Error posting message to Slack channel. Max retries limit was reached.') } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: Slack } ================================================ FILE: packages/components/nodes/SnowTrace/SnowTrace.ts ================================================ import axios, { AxiosRequestConfig, Method } from 'axios' import { etherscanAPIs, NETWORK } from '../../src' import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, serializeQueryParams } from '../../src/utils' class SnowTrace implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions: INodeParams[] credentials?: INodeParams[] networks?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'SnowTrace' this.name = 'snowtrace' this.icon = 'snowtrace.svg' this.type = 'action' this.category = 'Block Explorer' this.version = 1.0 this.description = 'Perform SnowTrace operations' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'API', name: 'api', type: 'options', options: [ { label: 'Get AVAX Balance for a Single Address', name: 'getAvaxBalance', description: 'Returns the AVAX balance of a given address.' }, { label: 'Get AVAX Balance for Multiple Addresses', name: 'getAvaxMultipleBalance', description: 'Returns the AVAX balance of given addresses.' }, { label: 'Get a list of "Normal" Transactions By Address', name: 'getNormalTransactions', description: 'Returns a list of "Normal" transaction by address.' }, { label: 'Get a list of "Internal" Transactions by Address', name: 'getInternalTransactions', description: 'Returns a list of "Internal" transaction by address.' }, { label: 'Get "Internal Transactions" by Transaction Hash', name: 'getInternalTransactionsByHash', description: 'Returns "Internal Transactions" by hash.' }, { label: 'Get "Internal Transactions" by Block Range', name: 'getInternalTransactionsByBlockRange', description: 'Returns "Internal Transactions" transaction by block range.' }, { label: 'Get a list of "ERC20 - Token Transfer Events" by Address', name: 'getErc20TokenTransferEvents', description: 'Returns a list of "ERC20 - Token Transfer Events" by address.' }, { label: 'Get a list of "ERC721 - Token Transfer Events" by Address', name: 'getErc721TokenTransferEvents', description: 'Returns a list of "ERC721 - Token Transfer Events" by address.' }, { label: 'Get list of Blocks Validated by Address', name: 'getBlocksValidated', description: 'Returns a list of blocks validated by address.' }, { label: 'Get Contract ABI for Verified Contract Source Codes', name: 'getContractABI', description: 'Returns the contract ABI for verified contract source codes.' } ], default: 'getAvaxBalance' } ] as INodeParams[] this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [ { label: 'Avalanche Mainnet', name: NETWORK.AVALANCHE }, { label: 'Avalanche Testnet', name: NETWORK.AVALANCHE_TESTNET } ], default: NETWORK.AVALANCHE } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'SnowTrace API Key', name: 'snowtraceApi' } ], default: 'snowtraceApi' } ] as INodeParams[] this.inputParameters = [ { label: 'Address', name: 'address', type: 'string', description: 'The address to check for balance', show: { 'actions.api': [ 'getAvaxBalance', 'getNormalTransactions', 'getInternalTransactions', 'getErc20TokenTransferEvents', 'getErc721TokenTransferEvents', 'getBlocksValidated', 'getContractABI' ] } }, { label: 'Addresses', name: 'addresses', type: 'json', placeholder: `[ '0x0000000000000000000000000000000000001004', '0x0000000000000000000000000000000000001000' ]`, description: 'The addresses to check for balance', show: { 'actions.api': ['getAvaxMultipleBalance'] } }, { label: 'Transaction Hash', name: 'hash', type: 'string', placeholder: '0x4d74a6fc84d57f18b8e1dfa07ee517c4feb296d16a8353ee41adc03669982028', description: 'The hash of the transaction', show: { 'actions.api': ['getInternalTransactionsByHash'] } }, { label: 'Start Block', name: 'startBlock', type: 'number', placeholder: '0', description: 'The starting block to check for internal transactions', show: { 'actions.api': ['getInternalTransactionsByBlockRange'] } }, { label: 'End Block', name: 'endBlock', type: 'number', placeholder: '2702578', description: 'The ending block to check for internal transactions', show: { 'actions.api': ['getInternalTransactionsByBlockRange'] } } ] as INodeParams[] } async fetch(url: string, queryParameters: any) { try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url, params: queryParameters, paramsSerializer: (params) => serializeQueryParams(params), headers: { 'Content-Type': 'application/json' } } const response = await axios(axiosConfig) return response.data } catch (error) { throw handleErrorMessage(error) } } getQueryParameters(api: string, value: any) { switch (api) { case 'getAvaxBalance': return { module: 'account', action: 'balance', tag: 'latest', address: value } case 'getAvaxMultipleBalance': return { module: 'account', action: 'balancemulti', tag: 'latest', address: value } case 'getNormalTransactions': return { module: 'account', action: 'txlist', address: value } case 'getInternalTransactions': return { module: 'account', action: 'txlistinternal', address: value } case 'getInternalTransactionsByHash': return { module: 'account', action: 'txlistinternal', txhash: value } case 'getInternalTransactionsByBlockRange': return { module: 'account', action: 'txlistinternal', startblock: value[0], endblock: value[1] } case 'getErc20TokenTransferEvents': return { module: 'account', action: 'tokentx', address: value } case 'getErc721TokenTransferEvents': return { module: 'account', action: 'tokennfttx', address: value } case 'getBlocksValidated': return { module: 'account', action: 'getminedblocks', address: value } case 'getContractABI': return { module: 'contract', action: 'getabi', address: value } default: return {} } } async run(nodeData: INodeData): Promise { const actionData = nodeData.actions const networksData = nodeData.networks const inputParametersData = nodeData.inputParameters const credentials = nodeData.credentials if (actionData === undefined || inputParametersData === undefined || credentials === undefined || networksData === undefined) { throw new Error('Required data missing') } const api = actionData.api as string const network = networksData.network as NETWORK const apiKey = credentials.apiKey as string const address = inputParametersData.address as string const addresses = (inputParametersData.addresses as string) || '[]' let addressesArray = [] if (addresses) addressesArray = JSON.parse(addresses.replace(/\s/g, '')) const hash = inputParametersData.hash as string const startBlock = inputParametersData.startBlock as number const endBlock = inputParametersData.endBlock as number const url = etherscanAPIs[network] as string let responseData: any const returnData: ICommonObject[] = [] let queryParameters: ICommonObject = {} switch (api) { case 'getContractABI': case 'getBlocksValidated': case 'getErc721TokenTransferEvents': case 'getErc20TokenTransferEvents': case 'getInternalTransactions': case 'getNormalTransactions': case 'getAvaxBalance': queryParameters = { ...this.getQueryParameters(api, address), apiKey } break case 'getAvaxMultipleBalance': queryParameters = { ...this.getQueryParameters(api, addressesArray.join(',')), apiKey } break case 'getInternalTransactionsByHash': queryParameters = { ...this.getQueryParameters(api, hash), apiKey } break case 'getInternalTransactionsByBlockRange': queryParameters = { ...this.getQueryParameters(api, [startBlock, endBlock]), apiKey } break default: break } responseData = await this.fetch(url, queryParameters) if (Array.isArray(responseData)) returnData.push(...responseData) else returnData.push(responseData) return responseData } } module.exports = { nodeClass: SnowTrace } ================================================ FILE: packages/components/nodes/Solidity/Solidity.ts ================================================ import { ICommonObject, IDbCollection, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, IWallet, NodeType } from '../../src/Interface' import { getNodeModulesPackagePath, handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import { ethers } from 'ethers' import * as fs from 'fs' import { ArbitrumNetworks, BSCNetworks, ETHNetworks, getNetworkProvider, getNetworkProvidersList, NETWORK, networkExplorers, networkProviderCredentials, NETWORK_PROVIDER, OptimismNetworks, PolygonNetworks } from '../../src/ChainNetwork' // @ts-expect-error no type definition import solc from 'solc' function findImports(_path: string) { // Remove all ../ from the path while (_path.includes('../')) { _path = _path.replace('../', '') } const filepath = getNodeModulesPackagePath(_path) const contents = fs.readFileSync(filepath).toString() return { contents } } class Solidity implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] networks?: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Solidity' this.name = 'solidity' this.icon = 'solidity.svg' this.type = 'action' this.category = 'Smart Contract' this.version = 1.0 this.description = 'Compile and Deploy Solidity Code' this.incoming = 1 this.outgoing = 1 this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...ETHNetworks, ...PolygonNetworks, ...ArbitrumNetworks, ...OptimismNetworks, ...BSCNetworks], default: 'goerli' }, { label: 'Network Provider', name: 'networkProvider', type: 'asyncOptions', loadMethod: 'getNetworkProviders' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'] } } ] as INodeParams[] this.credentials = [...networkProviderCredentials] as INodeParams[] this.inputParameters = [ { label: 'Solidity Code', name: 'code', type: 'code', placeholder: `// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MyToken is ERC20 { constructor() ERC20("MyToken", "MTK") {} }`, description: 'Custom Solidity code to compile and deploy' }, { label: 'Contract Name', name: 'contractName', description: 'Contract Name to deploy the Solidity Code', type: 'string', default: '', placeholder: 'MyContract' }, { label: 'Select Wallet', name: 'wallet', type: 'asyncOptions', description: 'Wallet account to deploy Solidity code', loadFromDbCollections: ['Wallet'], loadMethod: 'getWallets' }, { label: 'Constructor Parameters', name: 'parameters', type: 'json', placeholder: '[ "param1", "param2" ]', description: 'Input parameters for constructor', optional: true } ] as INodeParams[] } loadMethods = { async getWallets(nodeData: INodeData, dbCollection?: IDbCollection): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) { return returnData } try { if (dbCollection === undefined || !dbCollection || !dbCollection.Wallet) { return returnData } const wallets: IWallet[] = dbCollection.Wallet for (let i = 0; i < wallets.length; i += 1) { const wallet = wallets[i] const data = { label: `${wallet.name} (${wallet.network})`, name: JSON.stringify(wallet), description: wallet.address } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } }, async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK return getNetworkProvidersList(network) } } async run(nodeData: INodeData): Promise { const networksData = nodeData.networks const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (networksData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } try { const walletString = inputParametersData.wallet as string const walletDetails: IWallet = JSON.parse(walletString) const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') // Get wallet instance const walletCredential = JSON.parse(walletDetails.walletCredential) const wallet = new ethers.Wallet(walletCredential.privateKey as string, provider) // Get input parameters const code = inputParametersData.code as string const solidityContractName = inputParametersData.contractName as string const parameters = inputParametersData.parameters as string const input = { language: 'Solidity', sources: {}, settings: { outputSelection: { '*': { '*': ['*'] } } } } as any input.sources[solidityContractName + '.sol'] = { content: code } const output = JSON.parse(solc.compile(JSON.stringify(input), { import: findImports })) const contractOutput = output.contracts[solidityContractName + '.sol'] const contractName = Object.keys(contractOutput)[0] const bytecode = contractOutput[contractName].evm.bytecode.object const abi = contractOutput[contractName].abi const factory = new ethers.ContractFactory(abi, bytecode, wallet) let contractParameters: any[] = [] if (parameters) { contractParameters = JSON.parse(parameters) } let deployedContract: ethers.Contract if (contractParameters.length > 0) deployedContract = await factory.deploy(...contractParameters) else deployedContract = await factory.deploy() // The contract is NOT deployed yet; we must wait until it is mined await deployedContract.deployed() const returnItem: ICommonObject = { explorerLink: `${networkExplorers[network]}/address/${deployedContract.address}`, address: deployedContract.address, transactionHash: deployedContract.deployTransaction.hash } return returnNodeExecutionData(returnItem) } catch (e) { throw handleErrorMessage(e) } } } module.exports = { nodeClass: Solidity } ================================================ FILE: packages/components/nodes/Solscan/Solscan.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' import { OPERATION, SORT_BY, SORT_DIRECTION } from './constants' class Solscan implements INode { // properties label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number // parameter actions: INodeParams[] credentials?: INodeParams[] networks?: INodeParams[] inputParameters?: INodeParams[] constructor() { // properties this.label = 'Solscan' this.name = 'solscan' this.icon = 'solscan.png' this.type = 'action' this.category = 'Block Explorer' this.version = 1.0 this.description = 'Solscan Public API' this.incoming = 1 this.outgoing = 1 // parameter this.actions = [ { label: 'API', name: 'api', type: 'options', description: 'Choose the API to execute', options: [ // Account { label: 'Get token balances', name: OPERATION.GET_TOKEN_BALANCES, description: 'Get tokens balances of the given address' }, { label: 'Get transactions', name: OPERATION.GET_TRANSACTIONS, description: 'Get list of transactions of the given account. MaxLimit 50 records per request' }, { label: 'Get staking accounts', name: OPERATION.GET_STAKING_ACCOUNTS, description: 'Get staking accounts of the given account' }, { label: 'Get token transfers', name: OPERATION.GET_TOKEN_TRANSFERS, description: 'Get list of transactions make tokenBalance changes. MaxLimit 50 records per request' }, { label: 'Get Solana transfers', name: OPERATION.GET_SOL_TRANSFERS, description: 'Get list of SOL transfers. MaxLimit 50 records per request' }, { label: 'Get account info', name: OPERATION.GET_ACCOUNT_INFO, description: 'Get overall account information, including program account, NFT metadata information' }, // Transaction { label: 'Get last transactions', name: OPERATION.GET_LAST_TRANSACTIONS, description: 'Get last [limit] transactions' }, { label: 'Get transaction info', name: OPERATION.GET_TRANSACTION_INFO, description: 'Detail information of given transaction signature' }, // Token { label: 'Get token holder', name: OPERATION.GET_TOKEN_HOLDER, description: 'Get token holders list' }, { label: 'Get token info', name: OPERATION.GET_TOKEN_INFO, description: 'Get metadata of given token' }, { label: 'Get token list', name: OPERATION.GET_TOKENS, description: 'Get list of tokens. MaxLimit 50 records per request' }, // Market { label: 'Get token market info', name: OPERATION.GET_TOKEN_MARKET_INFO, description: 'Get market information of the given token' }, // Chain info { label: 'Get chain info', name: OPERATION.GET_CHAIN_INFO, description: 'Blockchain overall information' } ], default: OPERATION.GET_TOKEN_BALANCES } ] as INodeParams[] this.inputParameters = [ { label: 'Solana Address', name: 'address', type: 'string', description: 'The Solana address', show: { 'actions.api': [ OPERATION.GET_TOKEN_BALANCES, OPERATION.GET_TRANSACTIONS, OPERATION.GET_STAKING_ACCOUNTS, OPERATION.GET_TOKEN_TRANSFERS, OPERATION.GET_SOL_TRANSFERS, OPERATION.GET_ACCOUNT_INFO ] } }, { label: 'Limit', name: 'limit', type: 'number', default: 10, optional: true, show: { 'actions.api': [OPERATION.GET_TOKEN_HOLDER, OPERATION.GET_LAST_TRANSACTIONS] } }, { label: 'Offset', name: 'offset', type: 'number', default: 0, optional: true, show: { 'actions.api': [OPERATION.GET_TOKEN_HOLDER, OPERATION.GET_TOKENS] } }, { label: 'Transaction Signature', name: 'signature', type: 'string', show: { 'actions.api': [OPERATION.GET_TRANSACTION_INFO] } }, { label: 'Token Address', name: 'tokenAddress', type: 'string', show: { 'actions.api': [OPERATION.GET_TOKEN_HOLDER, OPERATION.GET_TOKEN_INFO, OPERATION.GET_TOKEN_MARKET_INFO] } }, { label: 'Sort By', name: 'sortBy', type: 'options', optional: true, options: SORT_BY, show: { 'actions.api': [OPERATION.GET_TOKENS] } }, { label: 'Sort Direction', name: 'direction', optional: true, type: 'options', options: SORT_DIRECTION, show: { 'actions.api': [OPERATION.GET_TOKENS] } } ] as INodeParams[] } getEndpoint(operation: string, options?: { address?: string; signature?: string; tokenAddress?: string }): string { const baseUrl = 'https://public-api.solscan.io' switch (operation) { case OPERATION.GET_TOKEN_BALANCES: return `${baseUrl}/account/tokens` case OPERATION.GET_TRANSACTIONS: return `${baseUrl}/account/transactions` case OPERATION.GET_STAKING_ACCOUNTS: return `${baseUrl}/account/stakeAccounts` case OPERATION.GET_TOKEN_TRANSFERS: return `${baseUrl}/account/splTransfers` case OPERATION.GET_SOL_TRANSFERS: return `${baseUrl}/account/solTransfers` case OPERATION.GET_ACCOUNT_INFO: return `${baseUrl}/account/${options?.address}` case OPERATION.GET_LAST_TRANSACTIONS: return `${baseUrl}/transaction/last` case OPERATION.GET_TRANSACTION_INFO: return `${baseUrl}/transaction/${options?.signature}` case OPERATION.GET_TOKEN_HOLDER: return `${baseUrl}/token/holders` case OPERATION.GET_TOKEN_INFO: return `${baseUrl}/token/meta` case OPERATION.GET_TOKENS: return `${baseUrl}/token/list` case OPERATION.GET_TOKEN_MARKET_INFO: return `${baseUrl}/market/token/${options?.tokenAddress}` case OPERATION.GET_CHAIN_INFO: return `${baseUrl}/chaininfo` default: return baseUrl } } async run(nodeData: INodeData): Promise { const actionData = nodeData.actions const inputParametersData = nodeData.inputParameters if (actionData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } const api = actionData.api as string const address = inputParametersData.address as string const limit = inputParametersData.limit as number const offset = inputParametersData.offset as number const signature = inputParametersData.signature as string const tokenAddress = inputParametersData.tokenAddress as string const sortBy = inputParametersData.sortBy as string const direction = inputParametersData.direction as string const url = this.getEndpoint(api, { address, signature, tokenAddress }) const queryParameters = { account: address, tokenAddress, limit, offset, sortBy, direction } const returnData: ICommonObject[] = [] let responseData: any try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url, params: queryParameters, paramsSerializer: (params) => serializeQueryParams(params), headers: { 'Content-Type': 'application/json' } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: Solscan } ================================================ FILE: packages/components/nodes/Solscan/constants.ts ================================================ export const OPERATION = { // Account GET_TOKEN_BALANCES: 'getTokenBalances', GET_TRANSACTIONS: 'getTransactions', GET_STAKING_ACCOUNTS: 'getStakingAccounts', GET_TOKEN_TRANSFERS: 'getTokenTransfers', GET_SOL_TRANSFERS: 'getSolTransfers', GET_ACCOUNT_INFO: 'getAccountInfo', // Transaction GET_LAST_TRANSACTIONS: 'getLastTransactions', GET_TRANSACTION_INFO: 'getTransactionInfo', // Token GET_TOKEN_HOLDER: 'getTokenHolder', GET_TOKEN_INFO: 'getTokenInfo', GET_TOKENS: 'getTokens', // Market GET_TOKEN_MARKET_INFO: 'getMarketTokenInfo', // Chain Info GET_CHAIN_INFO: 'getChainInfo' } as const export const SORT_BY = [ { label: 'Market cap', name: 'market_cap' }, { label: 'Volume', name: 'volume' }, { label: 'Holder', name: 'holder' }, { label: 'Price', name: 'price' }, { label: 'Price change 24 h', name: 'price_change_24h' }, { label: 'Price change 7 d', name: 'price_change_7d' }, { label: 'Price change 14 d', name: 'price_change_14d' }, { label: 'Price change 30 d', name: 'price_change_30d' }, { label: 'Price change 60 d', name: 'price_change_60d' }, { label: 'Price change 200 d', name: 'price_change_200d' }, { label: 'Price change 1 y', name: 'price_change_1y' } ] as const export const SORT_DIRECTION = [ { label: 'Desc', name: 'desc' }, { label: 'Asc', name: 'asc' } ] as const ================================================ FILE: packages/components/nodes/Teams/Teams.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' interface ITeamsWebhook { text?: string } class Teams implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number inputParameters?: INodeParams[] constructor() { this.label = 'Teams' this.name = 'teams' this.icon = 'teams.svg' this.type = 'action' this.category = 'Communication' this.version = 1.0 this.description = 'Post message in Teams channel' this.incoming = 1 this.outgoing = 1 this.inputParameters = [ { label: 'Webhook URL', name: 'webhookUrl', type: 'string', default: '', description: 'Webhook URL for the channel. Learn more: https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook' }, { label: 'Message', description: 'Message contents', name: 'text', type: 'string', default: '' } ] as INodeParams[] } async run(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters if (inputParametersData === undefined) { throw new Error('Required data missing') } const returnData: ICommonObject[] = [] const body: ITeamsWebhook = {} const webhookUrl = inputParametersData.webhookUrl as string const text = inputParametersData.text as string body.text = text let responseData: any let maxRetries = 5 do { try { const axiosConfig: AxiosRequestConfig = { method: 'POST' as Method, url: `${webhookUrl}`, data: body, headers: { 'Content-Type': 'application/json; charset=utf-8' } } const response = await axios(axiosConfig) responseData = response.data break } catch (error) { // Rate limit exceeded if (error.response && error.response.status === 429) { const retryAfter = error.response?.headers['retry-after'] || 60 await new Promise((resolve, _) => { setTimeout(() => { resolve() }, retryAfter * 1000) }) continue } throw handleErrorMessage(error) } } while (--maxRetries) if (maxRetries <= 0) { throw new Error('Error posting message to Teams channel. Max retries limit was reached.') } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: Teams } ================================================ FILE: packages/components/nodes/Telegram/Telegram.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import axios from 'axios' class Telegram implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number inputParameters: INodeParams[] credentials: INodeParams[] constructor() { this.label = 'Telegram' this.name = 'telegram' this.icon = 'telegram.svg' this.type = 'action' this.category = 'Communication' this.version = 1.0 this.description = 'Perform Telegram operations' this.incoming = 1 this.outgoing = 1 this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Telegram Bot Token', name: 'telegramApi' } ], placeholder: 'eg: 1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHI', default: '' } ] this.inputParameters = [ { label: 'Channel ID', name: 'channelID', type: 'string', placeholder: 'eg: MyAwesomeChannel', default: '', description: 'Your channel ID. See how to how to add bot in your channel.', optional: true }, { label: 'Content', description: 'Message contents (up to 2000 characters)', name: 'content', type: 'string', default: '' } ] } async run(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters const credentials = nodeData.credentials if (inputParametersData === undefined || credentials === undefined) { throw new Error('Required data missing') } const botToken = credentials.botToken as string const channelID = inputParametersData.channelID as string const content = inputParametersData.content as string const returnData: ICommonObject[] = [] try { const response = await axios.get(`https://api.telegram.org/bot${botToken}/sendMessage?chat_id=@${channelID}&text=${content}`) returnData.push(response.data) } catch (error) { throw handleErrorMessage(error) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: Telegram } ================================================ FILE: packages/components/nodes/ThirdWeb/ThirdWeb.ts ================================================ import { ICommonObject, IDbCollection, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, IWallet, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import { NodeVM } from 'vm2' import { networkLookup, prebuiltType, ThirdWebSupportedNetworks, ThirdWebSupportedPrebuiltContract } from './supportedNetwork' import { MarketplaceContractDeployMetadata, MultiwrapContractDeployMetadata, NFTContractDeployMetadata, SplitContractDeployMetadata, ThirdwebSDK as ThirdwebEVMSDK, TokenContractDeployMetadata, VoteContractDeployMetadata } from '@thirdweb-dev/sdk' import { marketplaceParameters, multiWrapParameters, nftDropParameters, splitContractParameters, tokenDropParameters, voteParameters } from './constants' import { networkExplorers } from '../../src/ChainNetwork' //import { ThirdwebSDK as ThirdwebSolanaSDK } from '@thirdweb-dev/sdk/solana' class ThirdWeb implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number actions?: INodeParams[] networks?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'ThirdWeb' this.name = 'thirdWeb' this.icon = 'thirdweb.svg' this.type = 'action' this.category = 'Development' this.version = 2.0 this.description = 'Execute ThirdWeb SDK code snippet' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Operation', name: 'operation', type: 'options', options: [ { label: 'Read data from contracts', name: 'read' }, { label: 'Execute transactions on contracts', name: 'execute' }, { label: 'Deploy new contract', name: 'deploy' } ], default: 'read' } ] as INodeParams[] this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...ThirdWebSupportedNetworks], default: 'mainnet' } ] as INodeParams[] this.inputParameters = [ { label: 'Contract Address', name: 'contract', type: 'string', description: 'Navigate to ThirdWeb -> Code -> Getting started -> Javascript, copy the address used in the code section', placeholder: '0x6a8c7F715D5f044437dA5b0576eD1289eC9b7eB6', show: { 'actions.operation': ['read', 'execute'] } }, { label: 'Prebuilt Contract Type', name: 'contractType', type: 'options', description: 'Navigate to ThirdWeb -> Code -> Getting started -> Javascript, select the prebuilt contract used in the code section. Ex: await sdk.getContract("0x...", "nft-drop")', options: [...ThirdWebSupportedPrebuiltContract] }, { label: 'Code', name: 'code', type: 'code', placeholder: `const ethers = require('ethers');\n\n//Get NFT Balance\nconst walletAddress = "0xE597E574889537A3A9120d1B5793cdFAEf6B6c10";\nconst balance = await contract.balanceOf(walletAddress);\nreturn ethers.utils.formatEther(balance);`, description: 'Custom code to run', show: { 'actions.operation': ['read', 'execute'] } }, { label: 'External Modules', name: 'external', type: 'json', placeholder: '["ethers"]', description: `Import installed dependencies within Outerbridge and use it within code. Ex: const ethers = require('ethers');`, optional: true, show: { 'actions.operation': ['read', 'execute'] } }, { label: 'Wallet Account', name: 'wallet', type: 'asyncOptions', description: 'Wallet account to execute transaction or deploy contract.', loadFromDbCollections: ['Wallet'], loadMethod: 'getWallets', show: { 'actions.operation': ['execute', 'deploy'] } }, ...nftDropParameters, ...marketplaceParameters, ...multiWrapParameters, ...splitContractParameters, ...tokenDropParameters, ...voteParameters ] as INodeParams[] } loadMethods = { async getWallets(nodeData: INodeData, dbCollection?: IDbCollection): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) { return returnData } try { if (dbCollection === undefined || !dbCollection || !dbCollection.Wallet) { return returnData } const wallets: IWallet[] = dbCollection.Wallet for (let i = 0; i < wallets.length; i += 1) { const wallet = wallets[i] const data = { label: `${wallet.name} (${wallet.network})`, name: JSON.stringify(wallet), description: wallet.address } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } } } async run(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters const actionsData = nodeData.actions const networksData = nodeData.networks if (inputParametersData === undefined || actionsData === undefined || networksData === undefined) { throw new Error('Required data missing') } const operation = actionsData.operation as string const contractAddress = inputParametersData.contract as string const contractType = inputParametersData.contractType as prebuiltType const network = networksData.network as string const walletString = inputParametersData.wallet as string const imageType = inputParametersData.imageType as string const name = inputParametersData.name as string const description = inputParametersData.description as string const symbol = inputParametersData.symbol as string const primary_sale_recipient = inputParametersData.primary_sale_recipient as string const fee_recipient = inputParametersData.fee_recipient as string const seller_fee_basis_points = inputParametersData.seller_fee_basis_points as string const platform_fee_recipient = inputParametersData.platform_fee_recipient as string const platform_fee_basis_points = inputParametersData.platform_fee_basis_points as string const external_link = inputParametersData.external_link as string const trusted_forwarders = inputParametersData.trusted_forwarders as string const recipients = inputParametersData.recipients as string const voting_token_address = inputParametersData.voting_token_address as string const proposal_token_threshold = inputParametersData.proposal_token_threshold as string const voting_delay_in_blocks = inputParametersData.voting_delay_in_blocks as string const voting_period_in_blocks = inputParametersData.voting_period_in_blocks as string const voting_quorum_fraction = inputParametersData.voting_quorum_fraction as string let responseData: any const returnData: ICommonObject[] = [] if (operation === 'read' || operation === 'execute') { const contract = await getThirdWebSDK(operation, network, contractAddress, contractType, walletString) // Global object const sandbox = { $nodeData: nodeData, $contract: contract } const options = { console: 'inherit', sandbox, require: { external: false as boolean | { modules: string[] }, builtin: ['*'] } } as any const code = inputParametersData.code as string const external = inputParametersData.external as string if (external) { const deps = JSON.parse(external) if (deps && deps.length) { options.require.external = { modules: deps } } } const vm = new NodeVM(options) try { if (!code) responseData = [] else { const initCode = `const contract = $contract;\n` responseData = await vm.run(`module.exports = async function() {${initCode}${code}}()`, __dirname) } } catch (e) { return Promise.reject(e) } } else if (operation === 'deploy') { const walletDetails: IWallet = JSON.parse(walletString) const walletCredential = JSON.parse(walletDetails.walletCredential) const sdk = ThirdwebEVMSDK.fromPrivateKey(walletCredential.privateKey, network) if ( contractType === 'nft-drop' || contractType === 'nft-collection' || contractType === 'edition' || contractType === 'edition-drop' || contractType === 'pack' || contractType === 'signature-drop' ) { const metadata: NFTContractDeployMetadata = { name, primary_sale_recipient } if (imageType) metadata.image = getImage(imageType, inputParametersData) if (symbol) metadata.symbol = symbol if (description) metadata.description = description if (fee_recipient) metadata.fee_recipient = fee_recipient if (seller_fee_basis_points) metadata.seller_fee_basis_points = parseInt(seller_fee_basis_points, 10) if (platform_fee_recipient) metadata.platform_fee_recipient = platform_fee_recipient if (platform_fee_basis_points) metadata.platform_fee_basis_points = parseInt(platform_fee_basis_points, 10) if (external_link) metadata.external_link = external_link if (trusted_forwarders) { try { metadata.trusted_forwarders = JSON.parse(trusted_forwarders) } catch (error) { throw handleErrorMessage(error) } } let contractAddress = '' if (contractType === 'nft-drop') contractAddress = await sdk.deployer.deployNFTDrop(metadata) else if (contractType === 'nft-collection') contractAddress = await sdk.deployer.deployNFTCollection(metadata) else if (contractType === 'edition') contractAddress = await sdk.deployer.deployEdition(metadata) else if (contractType === 'edition-drop') contractAddress = await sdk.deployer.deployEditionDrop(metadata) else if (contractType === 'pack') contractAddress = await sdk.deployer.deployPack(metadata) else if (contractType === 'signature-drop') contractAddress = await sdk.deployer.deploySignatureDrop(metadata) const returnItem: ICommonObject = { address: contractAddress, explorerLink: `${networkExplorers[networkLookup[network]]}/address/${contractAddress}` } return returnNodeExecutionData(returnItem) } else if (contractType === 'marketplace') { const metadata: MarketplaceContractDeployMetadata = { name } if (imageType) metadata.image = getImage(imageType, inputParametersData) if (description) metadata.description = description if (platform_fee_recipient) metadata.platform_fee_recipient = platform_fee_recipient if (platform_fee_basis_points) metadata.platform_fee_basis_points = parseInt(platform_fee_basis_points, 10) if (external_link) metadata.external_link = external_link if (trusted_forwarders) { try { metadata.trusted_forwarders = JSON.parse(trusted_forwarders) } catch (error) { throw handleErrorMessage(error) } } const contractAddress = await sdk.deployer.deployMarketplace(metadata) const returnItem: ICommonObject = { address: contractAddress, explorerLink: `${networkExplorers[networkLookup[network]]}/address/${contractAddress}` } return returnNodeExecutionData(returnItem) } else if (contractType === 'multiwrap') { const metadata: MultiwrapContractDeployMetadata = { name } if (imageType) metadata.image = getImage(imageType, inputParametersData) if (symbol) metadata.symbol = symbol if (description) metadata.description = description if (fee_recipient) metadata.fee_recipient = fee_recipient if (seller_fee_basis_points) metadata.seller_fee_basis_points = parseInt(seller_fee_basis_points, 10) if (external_link) metadata.external_link = external_link if (trusted_forwarders) { try { metadata.trusted_forwarders = JSON.parse(trusted_forwarders) } catch (error) { throw handleErrorMessage(error) } } const contractAddress = await sdk.deployer.deployMultiwrap(metadata) const returnItem: ICommonObject = { address: contractAddress, explorerLink: `${networkExplorers[networkLookup[network]]}/address/${contractAddress}` } return returnNodeExecutionData(returnItem) } else if (contractType === 'split') { let listOfRecipients = [] if (recipients) { try { listOfRecipients = JSON.parse(recipients.replace(/\s/g, '')) } catch (error) { throw handleErrorMessage(error) } } const metadata: SplitContractDeployMetadata = { name, recipients: listOfRecipients } if (imageType) metadata.image = getImage(imageType, inputParametersData) if (description) metadata.description = description if (external_link) metadata.external_link = external_link if (trusted_forwarders) { try { metadata.trusted_forwarders = JSON.parse(trusted_forwarders) } catch (error) { throw handleErrorMessage(error) } } const contractAddress = await sdk.deployer.deploySplit(metadata) const returnItem: ICommonObject = { address: contractAddress, explorerLink: `${networkExplorers[networkLookup[network]]}/address/${contractAddress}` } return returnNodeExecutionData(returnItem) } else if (contractType === 'token' || contractType === 'token-drop') { const metadata: TokenContractDeployMetadata = { name, primary_sale_recipient } if (imageType) metadata.image = getImage(imageType, inputParametersData) if (symbol) metadata.symbol = symbol if (description) metadata.description = description if (platform_fee_recipient) metadata.platform_fee_recipient = platform_fee_recipient if (platform_fee_basis_points) metadata.platform_fee_basis_points = parseInt(platform_fee_basis_points, 10) if (external_link) metadata.external_link = external_link if (trusted_forwarders) { try { metadata.trusted_forwarders = JSON.parse(trusted_forwarders) } catch (error) { throw handleErrorMessage(error) } } let contractAddress = '' if (contractType === 'token') contractAddress = await sdk.deployer.deployToken(metadata) else if (contractType === 'token-drop') contractAddress = await sdk.deployer.deployTokenDrop(metadata) const returnItem: ICommonObject = { address: contractAddress, explorerLink: `${networkExplorers[networkLookup[network]]}/address/${contractAddress}` } return returnNodeExecutionData(returnItem) } else if (contractType === 'vote') { const metadata: VoteContractDeployMetadata = { name, voting_token_address } if (imageType) metadata.image = getImage(imageType, inputParametersData) if (description) metadata.description = description if (voting_delay_in_blocks) metadata.voting_delay_in_blocks = parseInt(voting_delay_in_blocks, 10) if (voting_period_in_blocks) metadata.voting_period_in_blocks = parseInt(voting_period_in_blocks, 10) if (voting_quorum_fraction) metadata.voting_quorum_fraction = parseInt(voting_quorum_fraction, 10) if (proposal_token_threshold) metadata.proposal_token_threshold = proposal_token_threshold if (external_link) metadata.external_link = external_link if (trusted_forwarders) { try { metadata.trusted_forwarders = JSON.parse(trusted_forwarders) } catch (error) { throw handleErrorMessage(error) } } const contractAddress = await sdk.deployer.deployVote(metadata) const returnItem: ICommonObject = { address: contractAddress, explorerLink: `${networkExplorers[networkLookup[network]]}/address/${contractAddress}` } return returnNodeExecutionData(returnItem) } } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } const getImage = (imageType: string, inputParametersData: ICommonObject) => { let image if (imageType === 'url') { image = inputParametersData.imageURL as string } else if (imageType === 'base64') { const imageBase64 = inputParametersData.imageBase64 as string const splitDataURI = imageBase64.split(',') image = Buffer.from(splitDataURI.pop() || '', 'base64') } return image } const getThirdWebSDK = async ( operation: string, network: string, contractAddress: string, contractType: prebuiltType, walletString: string ) => { if (operation === 'read') { //return await ThirdwebSolanaSDK.fromNetwork(network).getProgram(contractAddress, contractType as any) return await new ThirdwebEVMSDK(network).getContract(contractAddress, contractType) } else { const walletDetails: IWallet = JSON.parse(walletString) const walletCredential = JSON.parse(walletDetails.walletCredential) //return await ThirdwebSolanaSDK.fromPrivateKey(walletCredential.privateKey, network).getProgram(contractAddress, contractType as any) return await ThirdwebEVMSDK.fromPrivateKey(walletCredential.privateKey, network).getContract(contractAddress, contractType) } } module.exports = { nodeClass: ThirdWeb } ================================================ FILE: packages/components/nodes/ThirdWeb/ThirdWebTrigger.ts ================================================ import { INode, INodeData, INodeOptionsValue, INodeParams, IProviders, NodeType } from '../../src/Interface' import { returnNodeExecutionData } from '../../src/utils' import EventEmitter from 'events' import { ThirdwebSDK as ThirdwebEVMSDK } from '@thirdweb-dev/sdk' import { editionDropEvents, editionEvents, marketplaceEvents, multiWrapEvents, nftCollectionEvents, nftDropEvents, packEvents, prebuiltType, signatureDropEvents, splitEvents, ThirdWebSupportedNetworks, ThirdWebSupportedPrebuiltContract, tokenDropEvents, tokenEvents, voteEvents } from './supportedNetwork' class ThirdWebEventTrigger extends EventEmitter implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] actions?: INodeParams[] providers: IProviders constructor() { super() this.label = 'ThirdWeb Event Trigger' this.name = 'thirdWebEventTrigger' this.icon = 'thirdweb.svg' this.type = 'trigger' this.category = 'Development' this.version = 1.0 this.description = 'Start workflow whenever a ThirdWeb event happened' this.incoming = 0 this.outgoing = 1 this.providers = {} this.actions = [ { label: 'Contract Address', name: 'contract', type: 'string', description: 'Navigate to ThirdWeb -> Code -> Getting started -> Javascript, copy the address used in the code section', placeholder: '0x6a8c7F715D5f044437dA5b0576eD1289eC9b7eB6' }, { label: 'Prebuilt Contract Type', name: 'contractType', type: 'options', description: 'Navigate to ThirdWeb -> Code -> Getting started -> Javascript, select the prebuilt contract used in the code section. Ex: await sdk.getContract("0x...", "nft-drop")', options: [...ThirdWebSupportedPrebuiltContract] }, { label: 'Event', name: 'event', type: 'asyncOptions', loadMethod: 'getEvents' } ] as INodeParams[] this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [...ThirdWebSupportedNetworks], default: 'mainnet' } ] as INodeParams[] } loadMethods = { async getEvents(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks const actionsData = nodeData.actions if (actionsData === undefined || networksData === undefined) { return returnData } const contractType = actionsData.contractType as prebuiltType switch (contractType) { case 'edition': return editionEvents case 'edition-drop': return editionDropEvents case 'marketplace': return marketplaceEvents case 'multiwrap': return multiWrapEvents case 'nft-collection': return nftCollectionEvents case 'nft-drop': return nftDropEvents case 'pack': return packEvents case 'signature-drop': return signatureDropEvents case 'split': return splitEvents case 'token': return tokenEvents case 'token-drop': return tokenDropEvents case 'vote': return voteEvents default: return [] } } } async runTrigger(nodeData: INodeData): Promise { const networksData = nodeData.networks const actionsData = nodeData.actions if (networksData === undefined || actionsData === undefined) { throw new Error('Required data missing') } const network = networksData.network as string const emitEventKey = nodeData.emitEventKey as string const contractAddress = actionsData.contract as string const eventName = actionsData.event as string const contract = await new ThirdwebEVMSDK(network).getContract(contractAddress) let eventData = '' const provider = contract.events const filter = { network, eventName, contractAddress } /********** WORKAROUND FOR THIRDWEB REMOVEEVENTLISTENER BUG ********* ** If this emitEventKey hasn't been called before OR emitEventKey has been called but not this filter ** This prevents from adding multiple event listener **/ if ( !Object.prototype.hasOwnProperty.call(this.providers, emitEventKey) || !Object.prototype.hasOwnProperty.call(this.providers[emitEventKey].filter, JSON.stringify(filter)) ) { provider.addEventListener(eventName, (event) => { if (eventData !== JSON.stringify(event)) { eventData = JSON.stringify(event) if (JSON.stringify(this.providers[emitEventKey].filter.currentFilter) === JSON.stringify(filter)) { this.emit(emitEventKey, returnNodeExecutionData(event)) } } }) } /** Example of providers this.providers = { 'W03MAR23-LPQ88HJB_thirdWebEventTrigger_0': { provider: ContractEvents { contractWrapper: [ContractWrapper] }, filter: { '{"network":"mumbai","eventName":"TokensMinted","contractAddress":"0x..."}': { network: 'mumbai', eventName: 'TokensMinted', contractAddress: '0x...' }, '{"network":"mumbai","eventName":"PlatformFeeInfoUpdated","contractAddress":"0x.."}': { network: 'mumbai', eventName: 'PlatformFeeInfoUpdated', contractAddress: '0x...' }, currentFilter: { network: 'mumbai', eventName: 'PlatformFeeInfoUpdated', contractAddress: '0x...' }, } } } */ if (Object.prototype.hasOwnProperty.call(this.providers, emitEventKey)) { const newFilter = { ...this.providers[emitEventKey].filter, [JSON.stringify(filter)]: filter, currentFilter: filter } this.providers[emitEventKey] = { provider, filter: newFilter } } else { const newFilter = { [JSON.stringify(filter)]: filter, currentFilter: filter } this.providers[emitEventKey] = { provider, filter: newFilter } } } async removeTrigger(nodeData: INodeData): Promise { const emitEventKey = nodeData.emitEventKey as string if (Object.prototype.hasOwnProperty.call(this.providers, emitEventKey)) { /** Disabling this because thirdweb removeEventListener bug, it doesnt remove listener const provider = this.providers[emitEventKey].provider provider.removeEventListener(eventName, (event: any) => { console.log(event) }) **/ this.removeAllListeners(emitEventKey) } } } module.exports = { nodeClass: ThirdWebEventTrigger } ================================================ FILE: packages/components/nodes/ThirdWeb/constants.ts ================================================ import { notEmptyRegex } from '../../src' import { INodeParams } from '../../src/Interface' export const nftDropParameters = [ { label: 'Name', name: 'name', description: 'Name of the contract', type: 'string', placeholder: 'MyNFT', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['nft-drop', 'nft-collection', 'edition', 'edition-drop', 'pack', 'signature-drop'] } }, { label: 'Image Type', name: 'imageType', type: 'options', options: [ { label: 'Base64', name: 'base64', description: 'Base64 (raw) image' }, { label: 'URL', name: 'url', description: 'URL path of the image' } ], optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['nft-drop', 'nft-collection', 'edition', 'edition-drop', 'pack', 'signature-drop'] } }, { label: 'Image URL', name: 'imageURL', description: 'URL path of the image', type: 'string', placeholder: 'ipfs://QmSYJ5PJ6SriDWyXtzA62NDfCCMpZt5FWqh9Y5xmhXsWhS/SquirrelV1.png', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['nft-drop', 'nft-collection', 'edition', 'edition-drop', 'pack', 'signature-drop'], 'inputParameters.imageType': ['url'] } }, { label: 'Image Base64', name: 'imageBase64', description: 'Base64 (raw) image', type: 'string', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['nft-drop', 'nft-collection', 'edition', 'edition-drop', 'pack', 'signature-drop'], 'inputParameters.imageType': ['base64'] } }, { label: 'Description', name: 'description', description: 'Description of the contract', type: 'string', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['nft-drop', 'nft-collection', 'edition', 'edition-drop', 'pack', 'signature-drop'] } }, { label: 'Symbol', name: 'symbol', description: 'Symbol for the contract', type: 'string', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['nft-drop', 'nft-collection', 'edition', 'edition-drop', 'pack', 'signature-drop'] } }, { label: 'Primary Sale Recipient Address', name: 'primary_sale_recipient', description: 'The address that will receive the proceeds from primary sales', type: 'string', placeholder: '0xE597E474889537A3A9120d1B5793cdFAEf6B6c10', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['nft-drop', 'nft-collection', 'edition', 'edition-drop', 'pack', 'signature-drop'] } }, { label: 'Royalties Recipient Address', name: 'fee_recipient', description: 'The address that will receive the proceeds from secondary sales', type: 'string', placeholder: '0xE597E474889537A3A9120d1B5793cdFAEf6B6c10', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['nft-drop', 'nft-collection', 'edition', 'edition-drop', 'pack', 'signature-drop'] } }, { label: 'Royalties Percentage', name: 'seller_fee_basis_points', description: 'The percentage (in basis points) of royalties for secondary sales. Eg: 50 = 0.5%', type: 'number', placeholder: '100', show: { 'actions.operation': ['deploy'], 'inputParameters.fee_recipient': notEmptyRegex, 'inputParameters.contractType': ['nft-drop', 'nft-collection', 'edition', 'edition-drop', 'pack', 'signature-drop'] } }, { label: 'Platform Fee Recipient Address', name: 'platform_fee_recipient', description: 'The address that will receive the proceeds from platform fees', type: 'string', placeholder: '0xE597E474889537A3A9120d1B5793cdFAEf6B6c10', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['nft-drop', 'nft-collection', 'edition', 'edition-drop', 'pack', 'signature-drop'] } }, { label: 'Platform Fee Percentage', name: 'platform_fee_basis_points', description: 'The percentage (in basis points) of platform fees. Eg: 50 = 0.5%', type: 'number', placeholder: '100', show: { 'actions.operation': ['deploy'], 'inputParameters.platform_fee_recipient': notEmptyRegex, 'inputParameters.contractType': ['nft-drop', 'nft-collection', 'edition', 'edition-drop', 'pack', 'signature-drop'] } }, { label: 'External Link', name: 'external_link', description: 'Url for the contract', type: 'string', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['nft-drop', 'nft-collection', 'edition', 'edition-drop', 'pack', 'signature-drop'] } }, { label: 'Trusted Forwarders', name: 'trusted_forwarders', description: 'Custom gasless trusted forwarder addresses', type: 'json', optional: true, placeholder: '["0x359B1408130241E115EE2285bA2635ebFC2109C6", "0xE597E474889537A3A9120d1B5793cdFAEf6B6c10"]', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['nft-drop', 'nft-collection', 'edition', 'edition-drop', 'pack', 'signature-drop'] } } ] as INodeParams[] export const marketplaceParameters = [ { label: 'Name', name: 'name', description: 'Name of the contract', type: 'string', placeholder: 'MyMarketplace', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['marketplace'] } }, { label: 'Image Type', name: 'imageType', type: 'options', options: [ { label: 'Base64', name: 'base64', description: 'Base64 (raw) image' }, { label: 'URL', name: 'url', description: 'URL path of the image' } ], optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['marketplace'] } }, { label: 'Image URL', name: 'imageURL', description: 'URL path of the image', type: 'string', placeholder: 'ipfs://QmSYJ5PJ6SriDWyXtzA62NDfCCMpZt5FWqh9Y5xmhXsWhS/SquirrelV1.png', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['marketplace'], 'inputParameters.imageType': ['url'] } }, { label: 'Image Base64', name: 'imageBase64', description: 'Base64 (raw) image', type: 'string', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['marketplace'], 'inputParameters.imageType': ['base64'] } }, { label: 'Description', name: 'description', description: 'Description of the contract', type: 'string', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['marketplace'] } }, { label: 'Platform Fee Recipient Address', name: 'platform_fee_recipient', description: 'The address that will receive the proceeds from platform fees', type: 'string', placeholder: '0xE597E474889537A3A9120d1B5793cdFAEf6B6c10', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['marketplace'] } }, { label: 'Platform Fee Percentage', name: 'platform_fee_basis_points', description: 'The percentage (in basis points) of platform fees. Eg: 50 = 0.5%', type: 'number', placeholder: '100', show: { 'actions.operation': ['deploy'], 'inputParameters.platform_fee_recipient': notEmptyRegex, 'inputParameters.contractType': ['marketplace'] } }, { label: 'External Link', name: 'external_link', description: 'Url for the contract', type: 'string', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['marketplace'] } }, { label: 'Trusted Forwarders', name: 'trusted_forwarders', description: 'Custom gasless trusted forwarder addresses', type: 'json', optional: true, placeholder: '["0x359B1408130241E115EE2285bA2635ebFC2109C6", "0xE597E474889537A3A9120d1B5793cdFAEf6B6c10"]', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['marketplace'] } } ] as INodeParams[] export const multiWrapParameters = [ { label: 'Name', name: 'name', description: 'Name of the contract', type: 'string', placeholder: 'MyMultiWrap', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['multiwrap'] } }, { label: 'Image Type', name: 'imageType', type: 'options', options: [ { label: 'Base64', name: 'base64', description: 'Base64 (raw) image' }, { label: 'URL', name: 'url', description: 'URL path of the image' } ], optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['multiwrap'] } }, { label: 'Image URL', name: 'imageURL', description: 'URL path of the image', type: 'string', placeholder: 'ipfs://QmSYJ5PJ6SriDWyXtzA62NDfCCMpZt5FWqh9Y5xmhXsWhS/SquirrelV1.png', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['multiwrap'], 'inputParameters.imageType': ['url'] } }, { label: 'Image Base64', name: 'imageBase64', description: 'Base64 (raw) image', type: 'string', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['multiwrap'], 'inputParameters.imageType': ['base64'] } }, { label: 'Description', name: 'description', description: 'Description of the contract', type: 'string', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['multiwrap'] } }, { label: 'Symbol', name: 'symbol', description: 'Symbol for the contract', type: 'string', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['multiwrap'] } }, { label: 'Royalties Recipient Address', name: 'fee_recipient', description: 'The address that will receive the proceeds from secondary sales', type: 'string', placeholder: '0xE597E474889537A3A9120d1B5793cdFAEf6B6c10', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['multiwrap'] } }, { label: 'Royalties Percentage', name: 'seller_fee_basis_points', description: 'The percentage (in basis points) of royalties for secondary sales. Eg: 50 = 0.5%', type: 'number', placeholder: '100', show: { 'actions.operation': ['deploy'], 'inputParameters.fee_recipient': notEmptyRegex, 'inputParameters.contractType': ['multiwrap'] } }, { label: 'External Link', name: 'external_link', description: 'Url for the contract', type: 'string', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['multiwrap'] } }, { label: 'Trusted Forwarders', name: 'trusted_forwarders', description: 'Custom gasless trusted forwarder addresses', type: 'json', optional: true, placeholder: '["0x359B1408130241E115EE2285bA2635ebFC2109C6", "0xE597E474889537A3A9120d1B5793cdFAEf6B6c10"]', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['multiwrap'] } } ] as INodeParams[] export const splitContractParameters = [ { label: 'Name', name: 'name', description: 'Name of the contract', type: 'string', placeholder: 'MySplit', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['split'] } }, { label: 'Image Type', name: 'imageType', type: 'options', options: [ { label: 'Base64', name: 'base64', description: 'Base64 (raw) image' }, { label: 'URL', name: 'url', description: 'URL path of the image' } ], optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['split'] } }, { label: 'Image URL', name: 'imageURL', description: 'URL path of the image', type: 'string', placeholder: 'ipfs://QmSYJ5PJ6SriDWyXtzA62NDfCCMpZt5FWqh9Y5xmhXsWhS/SquirrelV1.png', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['split'], 'inputParameters.imageType': ['url'] } }, { label: 'Image Base64', name: 'imageBase64', description: 'Base64 (raw) image', type: 'string', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['split'], 'inputParameters.imageType': ['base64'] } }, { label: 'Description', name: 'description', description: 'Description of the contract', type: 'string', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['split'] } }, { label: 'Recipients', name: 'recipients', description: 'The list of recipients and their share of the split', type: 'json', placeholder: `[ { "address": '0x359B1408130241E115EE2285bA2635ebFC2109C6', // The recipient address "sharesBps": 500, // The shares in basis point (5% = 500) }, { "address": '0xE597E474889537A3A9120d1B5793cdFAEf6B6c10', "sharesBps": 400, } ] `, default: `[ { "address": "your-address", "sharesBps": 8000 }, { "address": "another-address", "sharesBps": 2000 } ]`, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['split'] } }, { label: 'External Link', name: 'external_link', description: 'Url for the contract', type: 'string', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['split'] } }, { label: 'Trusted Forwarders', name: 'trusted_forwarders', description: 'Custom gasless trusted forwarder addresses', type: 'json', optional: true, placeholder: '["0x359B1408130241E115EE2285bA2635ebFC2109C6", "0xE597E474889537A3A9120d1B5793cdFAEf6B6c10"]', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['split'] } } ] as INodeParams[] export const tokenDropParameters = [ { label: 'Name', name: 'name', description: 'Name of the contract', type: 'string', placeholder: 'MyToken', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['token-drop', 'token'] } }, { label: 'Image Type', name: 'imageType', type: 'options', options: [ { label: 'Base64', name: 'base64', description: 'Base64 (raw) image' }, { label: 'URL', name: 'url', description: 'URL path of the image' } ], optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['token-drop', 'token'] } }, { label: 'Image URL', name: 'imageURL', description: 'URL path of the image', type: 'string', placeholder: 'ipfs://QmSYJ5PJ6SriDWyXtzA62NDfCCMpZt5FWqh9Y5xmhXsWhS/SquirrelV1.png', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['token-drop', 'token'], 'inputParameters.imageType': ['url'] } }, { label: 'Image Base64', name: 'imageBase64', description: 'Base64 (raw) image', type: 'string', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['token-drop', 'token'], 'inputParameters.imageType': ['base64'] } }, { label: 'Description', name: 'description', description: 'Description of the contract', type: 'string', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['token-drop', 'token'] } }, { label: 'Symbol', name: 'symbol', description: 'Symbol for the contract', type: 'string', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['token-drop', 'token'] } }, { label: 'Primary Sale Recipient Address', name: 'primary_sale_recipient', description: 'The address that will receive the proceeds from primary sales', type: 'string', placeholder: '0xE597E474889537A3A9120d1B5793cdFAEf6B6c10', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['token-drop', 'token'] } }, { label: 'Platform Fee Recipient Address', name: 'platform_fee_recipient', description: 'The address that will receive the proceeds from platform fees', type: 'string', placeholder: '0xE597E474889537A3A9120d1B5793cdFAEf6B6c10', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['token-drop', 'token'] } }, { label: 'Platform Fee Percentage', name: 'platform_fee_basis_points', description: 'The percentage (in basis points) of platform fees. Eg: 50 = 0.5%', type: 'number', placeholder: '100', show: { 'actions.operation': ['deploy'], 'inputParameters.platform_fee_recipient': notEmptyRegex, 'inputParameters.contractType': ['token-drop', 'token'] } }, { label: 'External Link', name: 'external_link', description: 'Url for the contract', type: 'string', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['token-drop', 'token'] } }, { label: 'Trusted Forwarders', name: 'trusted_forwarders', description: 'Custom gasless trusted forwarder addresses', type: 'json', optional: true, placeholder: '["0x359B1408130241E115EE2285bA2635ebFC2109C6", "0xE597E474889537A3A9120d1B5793cdFAEf6B6c10"]', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['token-drop', 'token'] } } ] as INodeParams[] export const voteParameters = [ { label: 'Name', name: 'name', description: 'Name of the contract', type: 'string', placeholder: 'MyVoteDAO', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['vote'] } }, { label: 'Image Type', name: 'imageType', type: 'options', options: [ { label: 'Base64', name: 'base64', description: 'Base64 (raw) image' }, { label: 'URL', name: 'url', description: 'URL path of the image' } ], optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['vote'] } }, { label: 'Image URL', name: 'imageURL', description: 'URL path of the image', type: 'string', placeholder: 'ipfs://QmSYJ5PJ6SriDWyXtzA62NDfCCMpZt5FWqh9Y5xmhXsWhS/SquirrelV1.png', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['vote'], 'inputParameters.imageType': ['url'] } }, { label: 'Image Base64', name: 'imageBase64', description: 'Base64 (raw) image', type: 'string', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['vote'], 'inputParameters.imageType': ['base64'] } }, { label: 'Description', name: 'description', description: 'Description of the contract', type: 'string', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['vote'] } }, { label: 'Voting Token Address', name: 'voting_token_address', description: 'The address of the governance token contract representing votes', type: 'string', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['vote'] } }, { label: 'Proposal Token Threshold', name: 'proposal_token_threshold', description: 'The minimum amount in governance token owned to be able to create a proposal', type: 'number', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['vote'] } }, { label: 'Voting Delay in Blocks', name: 'voting_delay_in_blocks', description: 'The delay in blocks before voting can begin on proposals Specified in number of blocks. Assuming block time of around 13.14 seconds, 1 day = 6570 blocks, 1 week = 45992 blocks.', type: 'number', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['vote'] } }, { label: 'Voting Period in Blocks', name: 'voting_period_in_blocks', description: 'The duration in blocks of the open voting window Specified in number of blocks. Assuming block time of around 13.14 seconds, 1 day = 6570 blocks, 1 week = 45992 blocks.', type: 'number', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['vote'] } }, { label: 'Voting Quorum Fraction', name: 'voting_quorum_fraction', description: 'The minimum fraction to be met to pass a proposal', type: 'number', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['vote'] } }, { label: 'External Link', name: 'external_link', description: 'Url for the contract', type: 'string', optional: true, show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['vote'] } }, { label: 'Trusted Forwarders', name: 'trusted_forwarders', description: 'Custom gasless trusted forwarder addresses', type: 'json', optional: true, placeholder: '["0x359B1408130241E115EE2285bA2635ebFC2109C6", "0xE597E474889537A3A9120d1B5793cdFAEf6B6c10"]', show: { 'actions.operation': ['deploy'], 'inputParameters.contractType': ['vote'] } } ] as INodeParams[] ================================================ FILE: packages/components/nodes/ThirdWeb/supportedNetwork.ts ================================================ import { INodeOptionsValue } from '../../src' import { NETWORK, NETWORK_LABEL } from '../../src/ChainNetwork' export type prebuiltType = | 'edition' | 'edition-drop' | 'marketplace' | 'multiwrap' | 'nft-collection' | 'nft-drop' | 'pack' | 'signature-drop' | 'split' | 'token' | 'token-drop' | 'vote' export const ThirdWebSupportedNetworks = [ { label: NETWORK_LABEL.MAINNET, name: 'mainnet' }, { label: NETWORK_LABEL.GÖRLI, name: 'goerli' }, { label: NETWORK_LABEL.MATIC, name: 'polygon' }, { label: NETWORK_LABEL.MATIC_MUMBAI, name: 'mumbai' }, { label: NETWORK_LABEL.ARBITRUM, name: NETWORK.ARBITRUM }, { label: NETWORK_LABEL.ARBITRUM_GOERLI, name: NETWORK.ARBITRUM_GOERLI }, { label: NETWORK_LABEL.AVALANCHE, name: NETWORK.AVALANCHE }, { label: NETWORK_LABEL.AVALANCHE_TESTNET, name: NETWORK.AVALANCHE_TESTNET }, { label: NETWORK_LABEL.OPTIMISM, name: NETWORK.OPTIMISM }, { label: NETWORK_LABEL.OPTIMISM_GOERLI, name: NETWORK.OPTIMISM_GOERLI }, { label: NETWORK_LABEL.FANTOM, name: NETWORK.FANTOM }, { label: NETWORK_LABEL.FANTOM_TESTNET, name: NETWORK.FANTOM_TESTNET }, { label: NETWORK_LABEL.BSC, name: 'binance' }, { label: NETWORK_LABEL.BSC_TESTNET, name: 'binance-testnet' } /* { label: NETWORK_LABEL.SOLANA, name: 'mainnet-beta' }, { label: NETWORK_LABEL.SOLANA_DEVNET, name: 'devnet' }, { label: NETWORK_LABEL.SOLANA_TESTNET, name: 'testnet' } */ ] as INodeOptionsValue[] export const ThirdWebSupportedPrebuiltContract = [ { label: 'Edition', name: 'edition', description: 'Create editions of ERC1155 tokens' }, { label: 'Edition Drop', name: 'edition-drop', description: 'Release ERC1155 tokens for a set price.' }, { label: 'Marketplace', name: 'marketplace', description: 'Buy and sell ERC721/ERC1155 tokens' }, { label: 'Multiwrap', name: 'multiwrap', description: 'Bundle multiple ERC721/ERC1155/ERC20 tokens into a single ERC721.' }, { label: 'NFT Collection', name: 'nft-collection', description: 'Create collection of unique NFTs.' }, { label: 'NFT Drop', name: 'nft-drop', description: 'Release collection of unique NFTs for a set price' }, { label: 'Pack', name: 'pack', description: 'Pack multiple tokens into ERC1155 NFTs that act as randomized loot boxes' }, { label: 'Signature Drop', name: 'signature-drop', description: 'Signature based minting of ERC721 tokens.' }, { label: 'Split', name: 'split', description: 'Distribute funds among multiple recipients' }, { label: 'Token', name: 'token', description: 'Create cryptocurrency compliant with ERC20 standard' }, { label: 'Token Drop', name: 'token-drop', description: 'Release new ERC20 tokens for a set price' }, { label: 'Vote', name: 'vote', description: 'Create and vote on proposals' } ] as INodeOptionsValue[] interface IThirdWebNetwork { [key: string]: NETWORK } export const networkLookup: IThirdWebNetwork = { mainnet: NETWORK.MAINNET, goerli: NETWORK.GÖRLI, polygon: NETWORK.MATIC, mumbai: NETWORK.MATIC_MUMBAI, binance: NETWORK.BSC, 'binance-testnet': NETWORK.BSC_TESTNET, [NETWORK.ARBITRUM]: NETWORK.ARBITRUM, [NETWORK.ARBITRUM_GOERLI]: NETWORK.ARBITRUM_GOERLI, [NETWORK.AVALANCHE]: NETWORK.AVALANCHE, [NETWORK.AVALANCHE_TESTNET]: NETWORK.AVALANCHE_TESTNET, [NETWORK.OPTIMISM]: NETWORK.OPTIMISM, [NETWORK.OPTIMISM_GOERLI]: NETWORK.OPTIMISM_GOERLI, [NETWORK.FANTOM]: NETWORK.FANTOM, [NETWORK.FANTOM_TESTNET]: NETWORK.FANTOM_TESTNET } export const nftDropEvents = [ { label: 'TokensClaimed', name: 'TokensClaimed' }, { label: 'TokensLazyMinted', name: 'TokensLazyMinted' }, { label: 'Transfer', name: 'Transfer' }, { label: 'Approval', name: 'Approval' }, { label: 'ApprovalForAll', name: 'ApprovalForAll' }, { label: 'ClaimConditionsUpdated', name: 'ClaimConditionsUpdated' }, { label: 'ContractURIUpdated', name: 'ContractURIUpdated' }, { label: 'DefaultRoyalty', name: 'DefaultRoyalty' }, { label: 'Initialized', name: 'Initialized' }, { label: 'MaxTotalSupplyUpdated', name: 'MaxTotalSupplyUpdated' }, { label: 'ClaimConditionsUpdated', name: 'ClaimConditionsUpdated' }, { label: 'OperatorRestriction', name: 'OperatorRestriction' }, { label: 'OwnerUpdated', name: 'OwnerUpdated' }, { label: 'PlatformFeeInfoUpdated', name: 'PlatformFeeInfoUpdated' }, { label: 'PrimarySaleRecipientUpdated', name: 'PrimarySaleRecipientUpdated' }, { label: 'RoleAdminChanged', name: 'RoleAdminChanged' }, { label: 'RoleGranted', name: 'RoleGranted' }, { label: 'RoleRevoked', name: 'RoleRevoked' }, { label: 'RoyaltyForToken', name: 'RoyaltyForToken' }, { label: 'TokenURIRevealed', name: 'TokenURIRevealed' } ] as INodeOptionsValue[] export const nftCollectionEvents = [ { label: 'TokensMinted', name: 'TokensMinted' }, { label: 'TokensMintedWithSignature', name: 'TokensMintedWithSignature' }, { label: 'Transfer', name: 'Transfer' }, { label: 'Approval', name: 'Approval' }, { label: 'ApprovalForAll', name: 'ApprovalForAll' }, { label: 'DefaultRoyalty', name: 'DefaultRoyalty' }, { label: 'Initialized', name: 'Initialized' }, { label: 'OperatorRestriction', name: 'OperatorRestriction' }, { label: 'OwnerUpdated', name: 'OwnerUpdated' }, { label: 'PlatformFeeInfoUpdated', name: 'PlatformFeeInfoUpdated' }, { label: 'PrimarySaleRecipientUpdated', name: 'PrimarySaleRecipientUpdated' }, { label: 'RoleAdminChanged', name: 'RoleAdminChanged' }, { label: 'RoleGranted', name: 'RoleGranted' }, { label: 'RoleRevoked', name: 'RoleRevoked' }, { label: 'RoyaltyForToken', name: 'RoyaltyForToken' } ] as INodeOptionsValue[] export const marketplaceEvents = [ { label: 'NewSale', name: 'NewSale' }, { label: 'NewOffer', name: 'NewOffer' }, { label: 'ListingAdded', name: 'ListingAdded' }, { label: 'ListingUpdated', name: 'ListingUpdated' }, { label: 'ListingRemoved', name: 'ListingRemoved' }, { label: 'AuctionClosed', name: 'AuctionClosed' }, { label: 'AuctionBuffersUpdated', name: 'AuctionBuffersUpdated' }, { label: 'PlatformFeeInfoUpdated', name: 'PlatformFeeInfoUpdated' }, { label: 'RoleAdminChanged', name: 'RoleAdminChanged' }, { label: 'RoleGranted', name: 'RoleGranted' }, { label: 'RoleRevoked', name: 'RoleRevoked' } ] as INodeOptionsValue[] export const editionDropEvents = [ { label: 'TokensClaimed', name: 'TokensClaimed' }, { label: 'TokensLazyMinted', name: 'TokensLazyMinted' }, { label: 'TransferBatch', name: 'TransferBatch' }, { label: 'TransferSingle', name: 'TransferSingle' }, { label: 'ApprovalForAll', name: 'ApprovalForAll' }, { label: 'ClaimConditionsUpdated', name: 'ClaimConditionsUpdated' }, { label: 'ContractURIUpdated', name: 'ContractURIUpdated' }, { label: 'DefaultRoyalty', name: 'DefaultRoyalty' }, { label: 'Initialized', name: 'Initialized' }, { label: 'MaxTotalSupplyUpdated', name: 'MaxTotalSupplyUpdated' }, { label: 'OperatorRestriction', name: 'OperatorRestriction' }, { label: 'OwnerUpdated', name: 'OwnerUpdated' }, { label: 'PlatformFeeInfoUpdated', name: 'PlatformFeeInfoUpdated' }, { label: 'PrimarySaleRecipientUpdated', name: 'PrimarySaleRecipientUpdated' }, { label: 'RoleAdminChanged', name: 'RoleAdminChanged' }, { label: 'RoleGranted', name: 'RoleGranted' }, { label: 'RoleRevoked', name: 'RoleRevoked' }, { label: 'RoyaltyForToken', name: 'RoyaltyForToken' }, { label: 'SaleRecipientForTokenUpdated', name: 'SaleRecipientForTokenUpdated' }, { label: 'URI', name: 'URI' } ] as INodeOptionsValue[] export const tokenEvents = [ { label: 'TokensMinted', name: 'TokensMinted' }, { label: 'TokensMintedWithSignature', name: 'TokensMintedWithSignature' }, { label: 'Transfer', name: 'Transfer' }, { label: 'Approval', name: 'Approval' }, { label: 'DelegateChanged', name: 'DelegateChanged' }, { label: 'DelegateVotesChanged', name: 'DelegateVotesChanged' }, { label: 'PlatformFeeInfoUpdated', name: 'PlatformFeeInfoUpdated' }, { label: 'PrimarySaleRecipientUpdated', name: 'PrimarySaleRecipientUpdated' }, { label: 'RoleAdminChanged', name: 'RoleAdminChanged' }, { label: 'RoleGranted', name: 'RoleGranted' }, { label: 'RoleRevoked', name: 'RoleRevoked' } ] as INodeOptionsValue[] export const tokenDropEvents = [ { label: 'TokensClaimed', name: 'TokensClaimed' }, { label: 'Transfer', name: 'Transfer' }, { label: 'Approval', name: 'Approval' }, { label: 'ClaimConditionsUpdated', name: 'ClaimConditionsUpdated' }, { label: 'ContractURIUpdated', name: 'ContractURIUpdated' }, { label: 'DelegateChanged', name: 'DelegateChanged' }, { label: 'DelegateVotesChanged', name: 'DelegateVotesChanged' }, { label: 'Initialized', name: 'Initialized' }, { label: 'MaxTotalSupplyUpdated', name: 'MaxTotalSupplyUpdated' }, { label: 'PlatformFeeInfoUpdated', name: 'PlatformFeeInfoUpdated' }, { label: 'PrimarySaleRecipientUpdated', name: 'PrimarySaleRecipientUpdated' }, { label: 'RoleAdminChanged', name: 'RoleAdminChanged' }, { label: 'RoleGranted', name: 'RoleGranted' }, { label: 'RoleRevoked', name: 'RoleRevoked' } ] as INodeOptionsValue[] export const editionEvents = [ { label: 'TokensMinted', name: 'TokensMinted' }, { label: 'TokensMintedWithSignature', name: 'TokensMintedWithSignature' }, { label: 'TransferBatch', name: 'TransferBatch' }, { label: 'TransferSingle', name: 'TransferSingle' }, { label: 'ApprovalForAll', name: 'ApprovalForAll' }, { label: 'DefaultRoyalty', name: 'DefaultRoyalty' }, { label: 'ApprovalForAll', name: 'ApprovalForAll' }, { label: 'FlatPlatformFeeUpdated', name: 'FlatPlatformFeeUpdated' }, { label: 'OperatorRestriction', name: 'OperatorRestriction' }, { label: 'OwnerUpdated', name: 'OwnerUpdated' }, { label: 'PlatformFeeInfoUpdated', name: 'PlatformFeeInfoUpdated' }, { label: 'PlatformFeeTypeUpdated', name: 'PlatformFeeTypeUpdated' }, { label: 'PrimarySaleRecipientUpdated', name: 'PrimarySaleRecipientUpdated' }, { label: 'RoleAdminChanged', name: 'RoleAdminChanged' }, { label: 'RoleGranted', name: 'RoleGranted' }, { label: 'RoleRevoked', name: 'RoleRevoked' }, { label: 'RoyaltyForToken', name: 'RoyaltyForToken' }, { label: 'URI', name: 'URI' } ] as INodeOptionsValue[] export const multiWrapEvents = [ { label: 'TokensUnwrapped', name: 'TokensUnwrapped' }, { label: 'TokensWrapped', name: 'TokensWrapped' }, { label: 'Transfer', name: 'Transfer' }, { label: 'Approval', name: 'Approval' }, { label: 'ApprovalForAll', name: 'ApprovalForAll' }, { label: 'ContractURIUpdated', name: 'ContractURIUpdated' }, { label: 'DefaultRoyalty', name: 'DefaultRoyalty' }, { label: 'Initialized', name: 'Initialized' }, { label: 'OwnerUpdated', name: 'OwnerUpdated' }, { label: 'RoleAdminChanged', name: 'RoleAdminChanged' }, { label: 'RoleGranted', name: 'RoleGranted' }, { label: 'RoleRevoked', name: 'RoleRevoked' }, { label: 'RoyaltyForToken', name: 'RoyaltyForToken' } ] as INodeOptionsValue[] export const packEvents = [ { label: 'PackCreated', name: 'PackCreated' }, { label: 'PackOpened', name: 'PackOpened' }, { label: 'PackUpdated', name: 'PackUpdated' }, { label: 'TransferBatch', name: 'TransferBatch' }, { label: 'TransferSingle', name: 'TransferSingle' }, { label: 'ApprovalForAll', name: 'ApprovalForAll' }, { label: 'ContractURIUpdated', name: 'ContractURIUpdated' }, { label: 'DefaultRoyalty', name: 'DefaultRoyalty' }, { label: 'OwnerUpdated', name: 'OwnerUpdated' }, { label: 'RoleAdminChanged', name: 'RoleAdminChanged' }, { label: 'RoleGranted', name: 'RoleGranted' }, { label: 'RoleRevoked', name: 'RoleRevoked' }, { label: 'RoyaltyForToken', name: 'RoyaltyForToken' }, { label: 'URI', name: 'URI' } ] as INodeOptionsValue[] export const signatureDropEvents = [ { label: 'TokensClaimed', name: 'TokensClaimed' }, { label: 'TokensLazyMinted', name: 'TokensLazyMinted' }, { label: 'TokensMintedWithSignature', name: 'TokensMintedWithSignature' }, { label: 'TokenURIRevealed', name: 'TokenURIRevealed' }, { label: 'Transfer', name: 'Transfer' }, { label: 'Approval', name: 'Approval' }, { label: 'ApprovalForAll', name: 'ApprovalForAll' }, { label: 'ClaimConditionUpdated', name: 'ClaimConditionUpdated' }, { label: 'ContractURIUpdated', name: 'ContractURIUpdated' }, { label: 'DefaultRoyalty', name: 'DefaultRoyalty' }, { label: 'Initialized', name: 'Initialized' }, { label: 'OwnerUpdated', name: 'OwnerUpdated' }, { label: 'PlatformFeeInfoUpdated', name: 'PlatformFeeInfoUpdated' }, { label: 'PrimarySaleRecipientUpdated', name: 'PrimarySaleRecipientUpdated' }, { label: 'RoleAdminChanged', name: 'RoleAdminChanged' }, { label: 'RoleGranted', name: 'RoleGranted' }, { label: 'RoleRevoked', name: 'RoleRevoked' }, { label: 'RoyaltyForToken', name: 'RoyaltyForToken' } ] as INodeOptionsValue[] export const splitEvents = [ { label: 'ERC20PaymentReleased', name: 'ERC20PaymentReleased' }, { label: 'PaymentReceived', name: 'PaymentReceived' }, { label: 'PaymentReleased', name: 'PaymentReleased' }, { label: 'PayeeAdded', name: 'PayeeAdded' }, { label: 'RoleAdminChanged', name: 'RoleAdminChanged' }, { label: 'RoleGranted', name: 'RoleGranted' }, { label: 'RoleRevoked', name: 'RoleRevoked' } ] as INodeOptionsValue[] export const voteEvents = [ { label: 'ProposalCreated', name: 'ProposalCreated' }, { label: 'ProposalExecuted', name: 'ProposalExecuted' }, { label: 'ProposalCanceled', name: 'ProposalCanceled' }, { label: 'ProposalThresholdSet', name: 'ProposalThresholdSet' }, { label: 'VoteCast', name: 'VoteCast' }, { label: 'VoteCastWithParams', name: 'VoteCastWithParams' }, { label: 'VotingDelaySet', name: 'VotingDelaySet' }, { label: 'VotingPeriodSet', name: 'VotingPeriodSet' }, { label: 'QuorumNumeratorUpdated', name: 'QuorumNumeratorUpdated' }, { label: 'Initialized', name: 'Initialized' } ] as INodeOptionsValue[] ================================================ FILE: packages/components/nodes/Twitter/Twitter.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' class Twitter implements INode { label: string name: string icon: string type: NodeType description: string version: number category: string incoming: number outgoing: number inputParameters?: INodeParams[] credentials?: INodeParams[] constructor() { this.label = 'Twitter' this.name = 'twitter' this.icon = 'Twitter-Logo.png' this.type = 'action' this.category = 'Communication' this.description = "Search Twitter User's tweets by keyword" this.version = 1.0 this.incoming = 1 this.outgoing = 1 this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Twitter Bearer Token', name: 'twitterApi' } ], default: 'twitterApi' } ] as INodeParams[] this.inputParameters = [ { label: 'Twitter ID', name: 'TwitterId', type: 'string', default: '', description: '' }, { label: 'Keyword', name: 'Keyword', type: 'string', default: '', description: 'Message contents (up to 512 characters long)' }, { label: 'From', name: 'fromDate', type: 'options', description: 'Date of start search', options: [ { label: 'From Today UTC', name: 'fromTodayUTC' } ], default: 'fromTodayUTC' } ] as INodeParams[] } async run(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters const credentials = nodeData.credentials if (inputParametersData === undefined || credentials === undefined) { throw new Error('Required data missing') } const returnData: ICommonObject[] = [] const bearerToken = credentials.bearerToken as string const TwitterId = inputParametersData.TwitterId as string const keyword = inputParametersData.Keyword as string const query = 'from:' + TwitterId + ' ' + keyword const timestamp = new Date() timestamp.setUTCHours(0, 0, 0, 0) let responseData: any let maxRetries = 5 do { try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://api.twitter.com/2/tweets/search/recent?query=${query}&start_time=${timestamp.toISOString()}&tweet.fields=created_at&sort_order=recency`, headers: { Authorization: `Bearer ${bearerToken}` } } const response = await axios(axiosConfig) responseData = response.data break } catch (error) { throw handleErrorMessage(error) } } while (--maxRetries) if (maxRetries <= 0) { throw new Error('Error getting message from twitter API. Max retries limit was reached.') } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: Twitter } ================================================ FILE: packages/components/nodes/Typeform/Typeform.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' class Typeform implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Typeform' this.name = 'typeform' this.icon = 'typeform-icon.svg' this.type = 'action' this.category = 'Communication' this.version = 1.0 this.description = 'Perform Typeform operations' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'API', name: 'api', type: 'options', options: [ { label: 'Get all forms', name: 'getAllForms', description: 'Returns all the forms associated with your account' }, { label: 'Get Typeform Responses', name: 'getTypeformResponses', description: 'Returns the submissions for your typeforms in JSON format' }, { label: 'Create Typeform ', name: 'createTypeform', description: 'Creates a typeform for you' } ], default: 'getAllForms' } ] as INodeParams[] this.credentials = [ // credentialMethod is mandatory field { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Typeform API Key', name: 'typeformApi' } ], default: 'typeformApi' } ] as INodeParams[] this.inputParameters = [ { label: 'Form Id', name: 'formId', type: 'string', description: 'The form id to retrieve all the responses to your typeform', show: { 'actions.api': ['getTypeformResponses'] } }, { label: 'Request Body', name: 'requestBody', type: 'json', description: 'The json object to create or update your typeform', show: { 'actions.api': ['createTypeform'] } } ] as INodeParams[] } async run(nodeData: INodeData): Promise { // function to start running the node const actionData = nodeData.actions const inputParametersData = nodeData.inputParameters const credentials = nodeData.credentials if (actionData === undefined || inputParametersData === undefined || credentials === undefined) { throw new Error('Required data missing') } // GET api const api = actionData.api as string // GET credentials const apiKey = credentials.apiKey as string // GET formId const formId = inputParametersData.formId as string let requestBody = inputParametersData.requestBody as string requestBody = requestBody ? requestBody.replace(/\s/g, '') : requestBody const returnData: ICommonObject[] = [] let responseData: any if (api === 'getAllForms') { try { const queryParameters = {} let url = `https://api.typeform.com/forms` const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url, params: queryParameters, paramsSerializer: (params) => serializeQueryParams(params), headers: { 'Content-Type': 'application/json', authorization: `bearer ${apiKey}` } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) returnData.push(...responseData) else returnData.push(responseData) return returnNodeExecutionData(returnData) } else if (api === 'getTypeformResponses') { try { const queryParameters = {} let url = `https://api.typeform.com/forms/${formId}/responses` const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url, params: queryParameters, paramsSerializer: (params) => serializeQueryParams(params), headers: { 'Content-Type': 'application/json', authorization: `bearer ${apiKey}` } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) returnData.push(...responseData) else returnData.push(responseData) return returnNodeExecutionData(returnData) } else if (api === 'createTypeform') { try { const body = JSON.parse(requestBody) let url = `https://api.typeform.com/forms` const axiosConfig: AxiosRequestConfig = { method: 'POST' as Method, url, data: Object.assign({}, body), headers: { 'Content-Type': 'application/json; charset=utf-8', authorization: `bearer ${apiKey}` } } const response = await axios(axiosConfig) responseData = response.data } catch (error) { throw handleErrorMessage(error) } if (Array.isArray(responseData)) returnData.push(...responseData) else returnData.push(responseData) return returnNodeExecutionData(returnData) } return returnNodeExecutionData(returnData) } } module.exports = { nodeClass: Typeform } ================================================ FILE: packages/components/nodes/TypeformWebhook/TypeformWebhook.ts ================================================ import { ICommonObject, INode, INodeData, INodeParams, IWebhookNodeExecutionData, NodeType } from '../../src/Interface' import { returnWebhookNodeExecutionData, handleErrorMessage } from '../../src/utils' import axios, { AxiosRequestConfig, Method } from 'axios' class TypeformWebhook implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions: INodeParams[] credentials?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Typeform Webhook' this.name = 'typeformWebhook' this.icon = 'typeform-webhook.svg' this.type = 'webhook' this.category = 'Communication' this.version = 1.0 this.description = 'Start workflow whenever Typeform webhook event happened' this.incoming = 0 this.outgoing = 1 this.actions = [ { label: 'Event', name: 'webhook_type', type: 'options', options: [ { label: 'Typeform Submission', name: 'typeformSubmission', description: 'Triggered anytime form typeform submit a response sent through typeform webhook.' } ], default: 'typeformSubmission' } ] as INodeParams[] this.credentials = [ // credentialMethod is mandatory field { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Typeform Access Token', name: 'typeformApi' } ], default: 'typeformApi' } ] as INodeParams[] this.inputParameters = [ { label: 'Form Id', name: 'formId', type: 'string', description: 'Unique ID for the form. Find in your form URL. For example, in the URL "https://mysite.typeform.com/to/u6nXL7" the form_id is u6nXL7', show: { 'actions.webhook_type': ['typeformSubmission'] } }, { label: 'Webhook Tag', name: 'tag', type: 'string', placeholder: 'mywebhook', description: 'The name you want to use for your webhook', show: { 'actions.webhook_type': ['typeformSubmission'] } } ] as INodeParams[] } webhookMethods = { async createWebhook(nodeData: INodeData, webhookFullUrl: string): Promise { // check for the webhooks const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters const actionsData = nodeData.actions if (inputParametersData === undefined || actionsData === undefined) { throw handleErrorMessage({ message: 'Required data missing' }) } if (credentials === undefined) { throw handleErrorMessage({ message: 'Missing credentials' }) } const accesToken = credentials.accessToken as string const tag = inputParametersData?.tag const formId = inputParametersData?.formId let webhookExist: boolean = false let webhookId: string = '' if (!accesToken) throw handleErrorMessage({ message: 'Access token required' }) if (!tag) throw handleErrorMessage({ message: 'Tag is required' }) if (!formId) throw handleErrorMessage({ message: 'FormId is required' }) const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://api.typeform.com/forms/${formId}/webhooks/${tag}`, headers: { Authorization: `Bearer ${accesToken}` } } try { const res = await axios(axiosConfig) webhookId = res?.data?.id webhookExist = true } catch (err) { if (err.response.status !== 404) throw new Error(err) } if (!webhookExist) { const axiosConfig: AxiosRequestConfig = { method: 'PUT' as Method, url: `https://api.typeform.com/forms/${formId}/webhooks/${tag}`, headers: { Authorization: `Bearer ${accesToken}` }, data: { enabled: true, url: webhookFullUrl } } try { const res = await axios(axiosConfig) webhookId = res?.data?.id } catch (err) { return } } return webhookId }, async deleteWebhook(nodeData: INodeData): Promise { // delete webhook const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters const actionsData = nodeData.actions if (inputParametersData === undefined || actionsData === undefined) { throw handleErrorMessage({ message: 'Required data missing' }) } if (credentials === undefined) { throw handleErrorMessage({ message: 'Missing credentials' }) } const accesToken = credentials.accessToken as string const tag = inputParametersData?.tag const formId = inputParametersData?.formId const axiosConfig: AxiosRequestConfig = { method: 'DELETE' as Method, url: `https://api.typeform.com/forms/${formId}/webhooks/${tag}`, headers: { Authorization: `Bearer ${accesToken}` } } try { await axios(axiosConfig) } catch (err) { return false } return true } } async runWebhook(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters const req = nodeData.req if (inputParametersData === undefined) { throw new Error('Required data missing') } if (req === undefined) { throw new Error('Missing request') } const returnData: ICommonObject[] = [] returnData.push({ headers: req?.headers, params: req?.params, query: req?.query, body: req?.body, rawBody: (req as any).rawBody, url: req?.url }) return returnWebhookNodeExecutionData(returnData) } } module.exports = { nodeClass: TypeformWebhook } ================================================ FILE: packages/components/nodes/Uniswap/Uniswap.ts ================================================ import { ContractInterface, ethers } from 'ethers' import { ICommonObject, IDbCollection, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, IWallet, NodeType } from '../../src/Interface' import { handleErrorMessage, returnNodeExecutionData } from '../../src/utils' import { networkExplorers, ETHNetworks, chainIdLookup, nativeCurrency, getNetworkProvidersList, NETWORK, getNetworkProvider, NETWORK_PROVIDER, NETWORK_LABEL } from '../../src/ChainNetwork' import IWETH from '@uniswap/v2-periphery/build/IWETH.json' import axios, { AxiosRequestConfig, Method } from 'axios' import { UniswapPair, UniswapPairSettings } from 'simple-uniswap-sdk' import { IToken, nativeTokens } from './nativeTokens' class Uniswap implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number networks?: INodeParams[] credentials?: INodeParams[] actions?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Uniswap' this.name = 'uniswap' this.icon = 'uniswap.png' this.type = 'action' this.category = 'Decentralized Finance' this.version = 1.0 this.description = 'Execute Uniswap operations' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Operation', name: 'operation', type: 'options', options: [ { label: 'Swap Tokens', name: 'swapTokens', description: 'Supports uniswap v2 and v3 prices together and returns the best price for swapping.' }, { label: 'Get Pairs', name: 'getPairs', description: 'Get most liquid pairs' }, { label: 'Custom Query', name: 'customQuery', description: 'Custom subgraph query to retrieve more information. https://docs.uniswap.org/protocol/V2/reference/API/queries' } ], default: 'swapTokens' } ] as INodeParams[] this.networks = [ { label: 'Network', name: 'network', type: 'options', options: [ { label: NETWORK_LABEL.MAINNET, name: NETWORK.MAINNET, parentGroup: 'Ethereum' } ], default: 'homestead', show: { 'actions.operation': ['getPairs', 'customQuery'] } }, { label: 'Network', name: 'network', type: 'options', options: [...ETHNetworks], default: 'homestead', show: { 'actions.operation': ['swapTokens'] } }, { label: 'Network Provider', name: 'networkProvider', type: 'asyncOptions', loadMethod: 'getNetworkProviders' }, { label: 'RPC Endpoint', name: 'jsonRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customRPC'], 'actions.operation': ['swapTokens'] } }, { label: 'Websocket Endpoint', name: 'websocketRPC', type: 'string', default: '', show: { 'networks.networkProvider': ['customWebsocket'], 'actions.operation': ['swapTokens'] } } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Alchemy API Key', name: 'alchemyApi', show: { 'networks.networkProvider': [NETWORK_PROVIDER.ALCHEMY] } }, { label: 'Infura API Key', name: 'infuraApi', show: { 'networks.networkProvider': [NETWORK_PROVIDER.INFURA] } }, { label: 'QuickNode Endpoints', name: 'quickNodeEndpoints', show: { 'networks.networkProvider': [NETWORK_PROVIDER.QUICKNODE] } } ], default: '', show: { 'networks.networkProvider': [NETWORK_PROVIDER.ALCHEMY, NETWORK_PROVIDER.INFURA, NETWORK_PROVIDER.QUICKNODE], 'actions.operation': ['swapTokens'] } } ] as INodeParams[] this.inputParameters = [ { label: 'From Token', name: 'fromToken', type: 'asyncOptions', description: 'Contract address of the token you want to convert FROM.', loadMethod: 'getTokens', show: { 'actions.operation': ['swapTokens'] } }, { label: 'To Token', name: 'toToken', type: 'asyncOptions', description: 'Contract address of the token you want to convert TO.', loadMethod: 'getTokens', show: { 'actions.operation': ['swapTokens'] } }, { label: 'Amount To Swap', name: 'amountToSwap', type: 'number', show: { 'actions.operation': ['swapTokens'] } }, { label: 'Select Wallet', name: 'wallet', type: 'asyncOptions', description: 'Wallet account to swap tokens.', loadFromDbCollections: ['Wallet'], loadMethod: 'getWallets', show: { 'actions.operation': ['swapTokens'] } }, { label: 'Query', name: 'query', type: 'string', rows: 10, show: { 'actions.operation': ['customQuery'] } }, { label: 'Slippage Tolerance (%)', name: 'slippage', type: 'number', default: 0.5, optional: true, description: 'How large of a price movement to tolerate before trade will fail to execute. Default to 0.5%.', show: { 'actions.operation': ['swapTokens'] } }, { label: 'Tx Deadline (mins)', name: 'deadlineMinutes', type: 'number', default: 20, optional: true, description: 'Minutes after which the transaction will fail. Default to 20 mins.', show: { 'actions.operation': ['swapTokens'] } }, { label: 'Disable Multihops', name: 'disableMultihops', type: 'boolean', default: false, optional: true, description: 'Restricts swaps to direct pairs only. Default to false.', show: { 'actions.operation': ['swapTokens'] } } ] as INodeParams[] } loadMethods = { async getTokens(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK try { const axiosConfig: AxiosRequestConfig = { method: 'GET' as Method, url: `https://tokens.uniswap.org` } const response = await axios(axiosConfig) const responseData = response.data let tokens: IToken[] = responseData.tokens const nativeToken: IToken = nativeTokens[network] // Add native token const data = { label: `${nativeToken.name} (${nativeToken.symbol})`, name: `${nativeToken.address};${nativeToken.symbol};${nativeToken.name}` } as INodeOptionsValue returnData.push(data) // Add other tokens tokens = tokens.filter((tkn) => tkn.chainId === chainIdLookup[network]) for (let i = 0; i < tokens.length; i += 1) { const token = tokens[i] const data = { label: `${token.name} (${token.symbol})`, name: `${token.address};${token.symbol};${token.name}` } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } }, async getWallets(nodeData: INodeData, dbCollection?: IDbCollection): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) { return returnData } try { if (dbCollection === undefined || !dbCollection || !dbCollection.Wallet) { return returnData } const wallets: IWallet[] = dbCollection.Wallet for (let i = 0; i < wallets.length; i += 1) { const wallet = wallets[i] const data = { label: `${wallet.name} (${wallet.network})`, name: JSON.stringify(wallet), description: wallet.address } as INodeOptionsValue returnData.push(data) } return returnData } catch (e) { return returnData } }, async getNetworkProviders(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const networksData = nodeData.networks if (networksData === undefined) return returnData const network = networksData.network as NETWORK return getNetworkProvidersList(network) } } async run(nodeData: INodeData): Promise { const networksData = nodeData.networks const actionsData = nodeData.actions const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (networksData === undefined || actionsData === undefined || inputParametersData === undefined) { throw new Error('Required data missing') } try { const network = networksData.network as NETWORK const provider = await getNetworkProvider( networksData.networkProvider as NETWORK_PROVIDER, network, credentials, networksData.jsonRPC as string, networksData.websocketRPC as string ) if (!provider) throw new Error('Invalid Network Provider') // Get operation const operation = actionsData.operation as string if (operation === 'swapTokens') { // Get fromTokenAddress const fromToken = inputParametersData.fromToken as string const [fromTokenContractAddress, fromTokenSymbol] = fromToken.split(';') // Get toTokenAddress const toToken = inputParametersData.toToken as string const [toTokenContractAddress, toTokenSymbol] = toToken.split(';') // Get wallet instance const walletString = inputParametersData.wallet as string const walletDetails: IWallet = JSON.parse(walletString) const walletCredential = JSON.parse(walletDetails.walletCredential) const wallet = new ethers.Wallet(walletCredential.privateKey as string, provider) // Get amount const amountToSwap = inputParametersData.amountToSwap as string if (fromTokenContractAddress.includes(`_${nativeCurrency[network]}`) && toTokenSymbol === 'WETH') { const wrapEthContract = new ethers.Contract(toTokenContractAddress, IWETH['abi'] as ContractInterface, wallet) const tx = await wrapEthContract.deposit({ value: ethers.utils.parseUnits(amountToSwap, 18) }) const approveReceipt = await tx.wait() if (approveReceipt.status === 0) throw new Error(`Failed to swap ETH to WETH`) const returnItem = { transactionHash: tx.hash, transactionReceipt: approveReceipt as any, link: `${networkExplorers[network]}/tx/${tx.hash}` } return returnNodeExecutionData(returnItem) } else if (toTokenContractAddress.includes(`_${nativeCurrency[network]}`) && fromTokenSymbol === 'WETH') { const wrapEthContract = new ethers.Contract(fromTokenContractAddress, IWETH['abi'] as ContractInterface, wallet) const tx = await wrapEthContract.withdraw(ethers.utils.parseUnits(amountToSwap, 18)) const approveReceipt = await tx.wait() if (approveReceipt.status === 0) throw new Error(`Failed to swap WETH to ETH`) const returnItem = { transactionHash: tx.hash, transactionReceipt: approveReceipt as any, link: `${networkExplorers[network]}/tx/${tx.hash}` } return returnNodeExecutionData(returnItem) } else { const slippage = inputParametersData.slippage as string const deadlineMinutes = inputParametersData.deadlineMinutes as number const disableMultihops = inputParametersData.disableMultihops as boolean const uniswapPair = new UniswapPair({ fromTokenContractAddress, toTokenContractAddress, ethereumAddress: wallet.address, ethereumProvider: provider, chainId: chainIdLookup[network] as number, settings: new UniswapPairSettings({ slippage: parseFloat(slippage) / 100.0 || 0.0005, deadlineMinutes: deadlineMinutes || 20, disableMultihops: disableMultihops || false }) }) const uniswapPairFactory = await uniswapPair.createFactory() const trade = await uniswapPairFactory.trade(amountToSwap) if (!trade.fromBalance.hasEnough) { throw new Error('You do not have enough from balance to execute this swap') } // Why we need two transactions: https://github.com/joshstevens19/simple-uniswap-sdk#ethers-example if (trade.approvalTransaction) { const approved = await wallet.sendTransaction(trade.approvalTransaction) await approved.wait() } const tradeTransaction = await wallet.sendTransaction(trade.transaction) const tradeReceipt = await tradeTransaction.wait() trade.destroy() const returnItem = { operation, transactionHash: tradeTransaction.hash, transactionReceipt: tradeReceipt as any, link: `${networkExplorers[network]}/tx/${tradeTransaction.hash}` } return returnNodeExecutionData(returnItem) } } else if (operation === 'getPairs' || operation === 'customQuery') { let query = '' if (operation === 'customQuery') query = inputParametersData.query as string else { query = `{ pairs( first: 100 orderBy: reserveUSD orderDirection: desc ) { id token0 { id symbol name } token1 { id symbol name } reserveUSD volumeUSD } }` } query = query.replace(/\s/g, ' ') const axiosConfig: AxiosRequestConfig = { method: 'POST' as Method, url: `https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2`, data: { query } } const response = await axios(axiosConfig) const responseData = response.data const returnData: ICommonObject[] = [] if (Array.isArray(responseData)) returnData.push(...responseData) else returnData.push(responseData) return returnNodeExecutionData(returnData) } return returnNodeExecutionData([]) } catch (e) { throw handleErrorMessage(e) } } } module.exports = { nodeClass: Uniswap } ================================================ FILE: packages/components/nodes/Uniswap/nativeTokens.ts ================================================ import { ETH, ChainId } from 'simple-uniswap-sdk' export interface INativeTokens { [key: string]: IToken } export interface IToken { address: string symbol: string name: string decimals: number chainId: number } export const nativeTokens: INativeTokens = { homestead: { address: ETH.MAINNET().contractAddress, symbol: 'ETH', name: 'ETH', decimals: 18, chainId: ChainId.MAINNET }, goerli: { address: ETH.GORLI().contractAddress, symbol: 'ETH', name: 'ETH', decimals: 18, chainId: ChainId.GÖRLI } } ================================================ FILE: packages/components/nodes/Wait/Wait.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeParams, NodeType } from '../../src/Interface' import { returnNodeExecutionData } from '../../src/utils' class Wait implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number inputParameters?: INodeParams[] constructor() { this.label = 'Wait' this.name = 'wait' this.icon = 'wait.svg' this.type = 'action' this.category = 'Utilities' this.version = 1.0 this.description = 'Wait before continuing with the execution' this.incoming = 1 this.outgoing = 1 this.inputParameters = [ { label: 'Unit', name: 'unit', type: 'options', options: [ { label: 'Seconds', name: 'seconds' }, { label: 'Minutes', name: 'minutes' }, { label: 'Hours', name: 'hours' }, { label: 'Days', name: 'days' } ], default: 'seconds', description: 'The time unit of the duration to wait' }, { label: 'Duration', name: 'duration', type: 'number', default: 10, description: 'Duration to wait before continuing with the execution' } ] } async run(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters if (inputParametersData === undefined) { throw new Error('Required data missing') } const startWaitDate = new Date().toUTCString() const unit = inputParametersData.unit as string let duration = (inputParametersData.duration as number) || 1 if (unit === 'minutes') { duration *= 60 } if (unit === 'hours') { duration *= 60 * 60 } if (unit === 'days') { duration *= 60 * 60 * 24 } duration *= 1000 const endWaitDate = new Date(new Date().getTime() + duration).toUTCString() const returnData: ICommonObject[] = [ { start: startWaitDate, end: endWaitDate, duration, unit } ] return new Promise((resolve, _) => { setTimeout(() => { resolve(returnNodeExecutionData(returnData)) }, duration) }) } } module.exports = { nodeClass: Wait } ================================================ FILE: packages/components/nodes/Webhook/Webhook.ts ================================================ import { ICommonObject, IDbCollection, INode, INodeData, INodeOptionsValue, INodeParams, IWebhookNodeExecutionData, NodeType } from '../../src/Interface' import { compareKeys, returnWebhookNodeExecutionData } from '../../src/utils' class Webhook implements INode { label: string name: string type: NodeType description?: string version: number icon: string category: string incoming: number outgoing: number inputParameters?: INodeParams[] constructor() { this.label = 'Webhook' this.icon = 'webhook.svg' this.name = 'webhook' this.type = 'webhook' this.category = 'Utilities' this.version = 2.0 this.description = 'Start workflow when webhook is called' this.incoming = 0 this.outgoing = 1 this.inputParameters = [ { label: 'HTTP Method', name: 'httpMethod', type: 'options', options: [ { label: 'GET', name: 'GET' }, { label: 'POST', name: 'POST' } ], default: 'GET', description: 'The HTTP method to listen to.' }, { label: 'Authorization', name: 'authorization', type: 'options', options: [ { label: 'API', name: 'headerAuth', description: 'Webhook header must contains "X-API-KEY" with matching key' }, { label: 'None', name: 'none' } ], default: 'none', description: 'The way to authorize incoming webhook.' }, { label: 'API key', name: 'apiKey', type: 'asyncOptions', loadMethod: 'getAPIKeys', description: 'Incoming call must consists header "x-api-key" with matching API key. You can create new key from the dashboard', show: { 'inputParameters.authorization': ['headerAuth'] } }, { label: 'Response Code', name: 'responseCode', type: 'number', default: 200, description: 'The HTTP response code to return when a HTTP request is made to this endpoint URL. Valid range: 1XX - 5XX' }, { label: 'What/How to Return', name: 'returnType', type: 'options', options: [ { label: 'Immediate Reponse', name: 'immediateResponse', description: 'Returns response immediately once webhook is called' }, { label: 'When Last Node Finishes', name: 'lastNodeResponse', description: 'Returns output response of the last executed node' } ], default: 'immediateResponse', description: 'What data or message, and how should Webhook node return upon successful calling' }, { label: 'Response Data', name: 'responseData', type: 'string', default: '', description: 'Custom response data to return when a HTTP request is made to this webhook endpoint URL. If not provided, default to: Webhook received!', optional: true, show: { 'inputParameters.returnType': ['immediateResponse'] } } ] } loadMethods = { async getAPIKeys(nodeData: INodeData, dbCollection?: IDbCollection, apiKeys?: ICommonObject[]): Promise { const returnData: INodeOptionsValue[] = [] if (!apiKeys || !apiKeys.length) return returnData for (let i = 0; i < apiKeys.length; i += 1) { const key = apiKeys[i] const data = { label: key.keyName, description: key.apiKey, name: key.apiSecret } as INodeOptionsValue returnData.push(data) } return returnData } } async runWebhook(nodeData: INodeData): Promise { const inputParametersData = nodeData.inputParameters const req = nodeData.req if (inputParametersData === undefined) { throw new Error('Required data missing') } if (req === undefined) { throw new Error('Missing request') } const responseData = (inputParametersData.responseData as string) || '' const authorization = inputParametersData.authorization as string const apiSecret = inputParametersData.apiKey as string const returnData: ICommonObject[] = [] if (authorization === 'headerAuth') { let suppliedKey = '' if (req.headers['X-API-KEY']) suppliedKey = req.headers['X-API-KEY'] as string if (req.headers['x-api-key']) suppliedKey = req.headers['x-api-key'] as string if (!suppliedKey) throw new Error('401: Missing API Key') const isKeyValid = compareKeys(apiSecret, suppliedKey) if (!isKeyValid) throw new Error('403: Unauthorized API Key') } returnData.push({ headers: req?.headers, params: req?.params, query: req?.query, body: req?.body, rawBody: (req as any).rawBody, url: req?.url }) return returnWebhookNodeExecutionData(returnData, responseData) } } module.exports = { nodeClass: Webhook } ================================================ FILE: packages/components/nodes/Xero/xero.ts ================================================ import { ICommonObject, INode, INodeData, INodeExecutionData, INodeOptionsValue, INodeParams, NodeType } from '../../src/Interface' import { handleErrorMessage, refreshOAuth2Token, returnNodeExecutionData, serializeQueryParams } from '../../src/utils' import axios, { AxiosRequestConfig, AxiosRequestHeaders, Method } from 'axios' class Xero implements INode { label: string name: string type: NodeType description: string version: number icon: string category: string incoming: number outgoing: number actions: INodeParams[] credentials?: INodeParams[] networks?: INodeParams[] inputParameters?: INodeParams[] constructor() { this.label = 'Xero' this.name = 'xero' this.icon = 'xero.svg' this.type = 'action' this.category = 'Accounting' this.version = 1.0 this.description = 'Perform Xero operations' this.incoming = 1 this.outgoing = 1 this.actions = [ { label: 'Operation', name: 'operation', type: 'options', options: [ { label: 'Get all invoices', name: 'getAllInvoices', description: 'Returns all the invoices from Xero account.' }, { label: 'Get single invoice', name: 'getSingleInvoice', description: 'Returns single invoice from Xero account.' }, { label: 'Create Invoice', name: 'createInvoice', description: 'Creates a new draft invoice.' }, { label: 'Send email', name: 'sendToEmail', description: 'Send email with invoice to primary email.' } ] } ] as INodeParams[] this.credentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Xero OAuth2', name: 'xeroOAuth2Api' } ], default: 'xeroOAuth2Api' } ] as INodeParams[] this.inputParameters = [ { label: 'Tenant', name: 'tenant', type: 'asyncOptions', loadMethod: 'getTenants' }, { label: 'Invoice', name: 'invoice', type: 'asyncOptions', loadMethod: 'getInvoices', show: { 'actions.operation': ['getSingleInvoice', 'sendToEmail'] } }, // Get existing contacts { label: 'Who do you want to bill?', name: 'contactName', type: 'asyncOptions', loadMethod: 'getContacts', show: { 'actions.operation': ['createInvoice'] } }, { label: 'Type of invoice', name: 'invoiceType', type: 'options', description: 'Which type of invoice? (ACCPAY or ACCREC)', options: [ { label: 'ACCPAY - Accounts Payable', name: 'ACCPAY' }, { label: 'ACCREC - Accounts Receivable', name: 'ACCREC' } ], show: { 'actions.operation': ['createInvoice'] } }, { label: 'Description', name: 'lineItem', type: 'string', description: 'Service or product to be billed.', placeholder: 'What service / product is to be billed?', show: { 'actions.operation': ['createInvoice'] } }, { label: 'Cost', name: 'unitAmount', type: 'number', description: 'Cost of product / service', show: { 'actions.operation': ['createInvoice'] } }, { label: 'Quantity', name: 'unitQuantity', type: 'number', description: 'Quantity of product / service', show: { 'actions.operation': ['createInvoice'] } } ] as INodeParams[] } loadMethods = { async getTenants(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const credentials = nodeData.credentials if (credentials === undefined) { return returnData } // Get credentials const token_type = credentials!.token_type as string const access_token = credentials!.access_token as string const headers: AxiosRequestHeaders = { 'Content-Type': 'application/json', Authorization: `${token_type} ${access_token}` } const axiosConfig: AxiosRequestConfig = { method: 'GET', url: `https://api.xero.com/connections`, headers } let maxRetries = 5 do { try { const response = await axios(axiosConfig) const responseData = response.data for (const tenant of responseData || []) { returnData.push({ label: tenant.tenantName as string, name: tenant.tenantId as string }) } return returnData } catch (e) { if (e.response && e.response.status === 401) { const { access_token } = await refreshOAuth2Token(credentials) headers['Authorization'] = `${token_type} ${access_token}` continue } return returnData } } while (--maxRetries) return returnData }, async getInvoices(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const credentials = nodeData.credentials const inputParameters = nodeData.inputParameters if (credentials === undefined || inputParameters === undefined) { return returnData } // Get credentials const tenantId = inputParameters.tenant as string const token_type = credentials!.token_type as string const access_token = credentials!.access_token as string const headers: AxiosRequestHeaders = { Accept: 'application/json', Authorization: `${token_type} ${access_token}`, 'Xero-tenant-id': tenantId } if (tenantId === undefined || token_type === undefined || access_token === undefined) { return returnData } const axiosConfig: AxiosRequestConfig = { method: 'GET', url: `https://api.xero.com/api.xro/2.0/Invoices`, headers } let maxRetries = 5 do { try { const response = await axios(axiosConfig) const responseData = response.data for (const invoice of responseData.Invoices || []) { returnData.push({ label: `${invoice.Contact.Name as string} - ${invoice.InvoiceNumber as string} `, name: invoice.InvoiceID as string }) } return returnData } catch (e) { if (e.response && e.response.status === 401) { const { access_token } = await refreshOAuth2Token(credentials) headers['Authorization'] = `${token_type} ${access_token}` continue } return returnData } } while (--maxRetries) return returnData }, async getContacts(nodeData: INodeData): Promise { const returnData: INodeOptionsValue[] = [] const credentials = nodeData.credentials const inputParameters = nodeData.inputParameters if (credentials === undefined || inputParameters === undefined) { return returnData } const tenantId = inputParameters.tenant as string const token_type = credentials!.token_type as string const access_token = credentials!.access_token as string const headers: AxiosRequestHeaders = { Accept: 'application/json', Authorization: `${token_type} ${access_token}`, 'Xero-tenant-id': tenantId } if (tenantId === undefined || token_type === undefined || access_token === undefined) { return returnData } const axiosConfig: AxiosRequestConfig = { method: 'GET', url: `https://api.xero.com/api.xro/2.0/contacts`, headers } let maxRetries = 5 do { try { const response = await axios(axiosConfig) const responseData = response.data for (const contact of responseData.Contacts || []) { returnData.push({ label: `${contact.Name as string}`, name: `${contact.ContactID as string}` }) } return returnData } catch (e) { if (e.response && e.response.status === 401) { const { access_token } = await refreshOAuth2Token(credentials) headers['Authorization'] = `${token_type} ${access_token}` continue } return returnData } } while (--maxRetries) return returnData } } async run(nodeData: INodeData): Promise { // function to start running node const actionsData = nodeData.actions const credentials = nodeData.credentials const inputParametersData = nodeData.inputParameters if (actionsData === undefined) { throw new Error('Required data missing!') } if (credentials === undefined) { throw new Error('Missing credentials!') } // Get Operation const operation = actionsData.operation as string // Get credentials https://developer.xero.com/documentation/guides/oauth2/auth-flow/#4-receive-your-tokens const token_type = credentials!.token_type as string const access_token = credentials!.access_token as string const returnData: ICommonObject[] = [] let responseData: any let url = '' const queryParameters: ICommonObject = {} const invoiceId = inputParametersData?.invoice as string const tenantId = inputParametersData?.tenant as string const invoiceType = inputParametersData?.invoiceType as string const contactId = inputParametersData?.contactName as string const lineItem = inputParametersData?.lineItem as string const unitAmount = inputParametersData?.unitAmount as string const unitQuantity = inputParametersData?.unitQuantity as string const lineItems = { Description: lineItem, UnitAmount: unitAmount, Quantity: unitQuantity } const Contact = { ContactID: contactId } let queryBody: any = {} let method: Method = 'POST' const headers: AxiosRequestHeaders = { Accept: 'application/json', authorization: `${token_type} ${access_token}`, 'Xero-tenant-id': tenantId } let maxRetries = 5 let oAuth2RefreshedData: any = {} do { try { if (operation === 'getAllInvoices') { method = 'GET' url = `https://api.xero.com/api.xro/2.0/Invoices` } else if (operation === 'getSingleInvoice') { method = 'GET' url = `https://api.xero.com/api.xro/2.0/Invoices/${invoiceId}` } else if (operation === 'sendToEmail') { // Will fail in the 'Test Node' because the test creates a req.body // The Endpoint requires and empty body to return successfully. method = 'POST' url = `https://api.xero.com/api.xro/2.0/Invoices/${invoiceId}/Email` } else if (operation === 'createInvoice') { method = 'POST' url = `https://api.xero.com/api.xro/2.0/Invoices` queryBody = { Type: invoiceType, LineItems: [lineItems], Contact: Contact } } const axiosConfig: AxiosRequestConfig = { method, url, headers } if (Object.keys(queryParameters).length > 0) { axiosConfig.params = queryParameters axiosConfig.paramsSerializer = (params) => serializeQueryParams(params) } if (Object.keys(queryBody).length > 0) { axiosConfig.data = queryBody } const response = await axios(axiosConfig) responseData = response.data break } catch (error) { if (error.response && error.response.status === 401) { const { access_token, expires_in } = await refreshOAuth2Token(credentials) headers['Authorization'] = `${token_type} ${access_token}` oAuth2RefreshedData = { access_token, expires_in } continue } throw handleErrorMessage(error) } } while (--maxRetries) if (maxRetries <= 0) { throw new Error('Error executing Xero node. Max retries limit was reached.') } if (Array.isArray(responseData)) { returnData.push(...responseData) } else { returnData.push(responseData) } return returnNodeExecutionData(returnData, oAuth2RefreshedData) } } module.exports = { nodeClass: Xero } ================================================ FILE: packages/components/package.json ================================================ { "name": "outerbridge-components", "version": "1.0.12", "description": "Outerbridge Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", "scripts": { "build": "tsc && gulp", "dev": "tsc --watch" }, "keywords": [], "homepage": "https://outerbridge.io", "author": { "name": "Henry Heng", "email": "henryheng@outerbridge.io" }, "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "@openzeppelin/contracts": "^4.7.3", "@thirdweb-dev/sdk": "^3.6.3", "@uniswap/v2-periphery": "^1.1.0-beta.0", "axios": "^0.27.2", "client-oauth2": "^4.3.3", "cron": "^1.8.3", "dotenv": "^16.0.0", "ethers": "^5.6.8", "express": "^4.17.3", "form-data": "^4.0.0", "imap": "^0.8.19", "jimp": "^0.16.2", "mailparser": "^3.5.0", "moment": "^2.29.3", "nodemailer": "^6.7.5", "simple-uniswap-sdk": "^3.7.0", "solc": "^0.8.15", "vm2": "^3.9.9", "ws": "^8.9.0" }, "devDependencies": { "@types/cron": "^1.7.3", "@types/gulp": "4.0.9", "@types/imap": "^0.8.35", "@types/mailparser": "^3.4.0", "@types/nodemailer": "^6.4.4", "@types/ws": "^8.5.3", "gulp": "^4.0.2", "typescript": "^4.8.4" } } ================================================ FILE: packages/components/src/ChainNetwork.ts ================================================ import { ICommonObject, INetworkMapping, INodeOptionsValue } from '.' import { ethers } from 'ethers' /** * ENUMS */ export enum NETWORK { MAINNET = 'homestead', GÖRLI = 'goerli', MATIC_MUMBAI = 'maticmum', MATIC = 'matic', OPTIMISM = 'optimism', OPTIMISM_GOERLI = 'optimism-goerli', ARBITRUM = 'arbitrum', ARBITRUM_GOERLI = 'arbitrum-goerli', ARBITRUM_NOVA = 'arbitrum-nova', BSC = 'bsc', BSC_TESTNET = 'bsc-testnet', AVALANCHE = 'avalanche', AVALANCHE_TESTNET = 'avalanche-testnet', FANTOM = 'fantom', FANTOM_TESTNET = 'fantom-testnet', CRONOS = 'cronos', CRONOS_TESTNET = 'cronos-testnet', GNOSIS = 'gnosis', CELO = 'celo', SOLANA = 'solana', SOLANA_TESTNET = 'solana-testnet', SOLANA_DEVNET = 'solana-devnet', HECO = 'heco', HARMONY = 'harmony', MOONRIVER = 'moonriver', MOONBEAM = 'moonbeam', METIS = 'metis', KLATYN_TESTNET = 'klaytn-testnet', FLOW = 'mainnet', FLOW_TESTNET = 'testnet' } export enum NETWORK_LABEL { MAINNET = 'Mainnet', GÖRLI = 'Goerli', MATIC_MUMBAI = 'Polygon Mumbai', MATIC = 'Polygon Mainnet', OPTIMISM = 'Optimism Mainnet', OPTIMISM_GOERLI = 'Optimism Goerli', ARBITRUM = 'Arbitrum Mainnet', ARBITRUM_GOERLI = 'Arbitrum Goerli', ARBITRUM_NOVA = 'Arbitrum Nova', BSC = 'Binance Smart Chain Mainnet', BSC_TESTNET = 'Binance Smart Chain Testnet', AVALANCHE = 'Avalanche Mainnet', AVALANCHE_TESTNET = 'Avalanche Testnet', FANTOM = 'Fantom Mainnet', FANTOM_TESTNET = 'Fantom Testnet', CRONOS = 'Cronos Mainnet', CRONOS_TESTNET = 'Cronos Testnet', GNOSIS = 'Gnosis Mainnet', CELO = 'Celo Mainnet', SOLANA = 'Solana Mainnet', SOLANA_TESTNET = 'Solana Testnet', SOLANA_DEVNET = 'Solana Devnet', HECO = 'Huobi ECO Chain Mainnet', HARMONY = 'Harmony Mainnet', MOONRIVER = 'Moonriver Mainnet', MOONBEAM = 'Moonbeam Mainnet', METIS = 'Metis Mainnet', KLATYN_TESTNET = 'Klaytn Baobab Testnet', FLOW = 'Flow', FLOW_TESTNET = 'Flow Testnet' } export enum NETWORK_PROVIDER { INFURA = 'infura', ALCHEMY = 'alchemy', QUICKNODE = 'quicknode', CLOUDFARE = 'cloudfare', CUSTOMRPC = 'customRPC', CUSTOMWSS = 'customWebsocket', BINANCE = 'binance', POLYGON = 'polygon', AVAX = 'avalanche', GNOSIS = 'gnosis', HECO = 'heco', FANTOM = 'fantom', SOLANA = 'solana', HARMONY = 'harmony', MOONRIVER = 'moonriver', MOONBEAM = 'moonbeam', METIS = 'metis', KLAYTN = 'klaytn', FLOW = 'flow', FLOW_TESTNET = 'flow_testnet' } export enum CHAIN_ID { MAINNET = 1, GÖRLI = 5, BINANCE_MAINNET = 56, BINANCE_TESTNET = 97, MATIC = 137, MATIC_MUMBAI = 80001, ARB_MAINNET = 42161, ARB_TESTNET_GOERLI = 421613, ARB_NOVA = 42170, OPT_MAINNET = 10, OPT_TESTNET_GOERLI = 420, CRONOS_MAINNET = 25, CRONOS_TESTNET = 338, AVALANCHE_MAINNET = 43114, AVALANCHE_TESTNET = 43113, FANTOM_MAINNET = 250, FANTOM_TESTNET = 4002, GNOSIS = 100, CELO = 42220, HECO = 128, HARMONY = 1666600000, MOONRIVER = 1285, MOONBEAM = 1284, METIS = 1088 } export enum DOMAIN_ID { MAINNET = 6648936, GÖRLI = 3331, MATIC_MUMBAI = 9991 } /** * Networks */ export const ETHNetworks = [ { label: NETWORK_LABEL.MAINNET, name: NETWORK.MAINNET, parentGroup: 'Ethereum' }, { label: NETWORK_LABEL.GÖRLI, name: NETWORK.GÖRLI, parentGroup: 'Ethereum' } ] as INodeOptionsValue[] export const BSCNetworks = [ { label: NETWORK_LABEL.BSC, name: NETWORK.BSC, parentGroup: 'Binance Smart Chain' }, { label: NETWORK_LABEL.BSC_TESTNET, name: NETWORK.BSC_TESTNET, parentGroup: 'Binance Smart Chain' } ] as INodeOptionsValue[] export const PolygonNetworks = [ { label: NETWORK_LABEL.MATIC, name: NETWORK.MATIC, parentGroup: 'Polygon' }, { label: NETWORK_LABEL.MATIC_MUMBAI, name: NETWORK.MATIC_MUMBAI, parentGroup: 'Polygon' } ] as INodeOptionsValue[] export const ArbitrumNetworks = [ { label: NETWORK_LABEL.ARBITRUM, name: NETWORK.ARBITRUM, parentGroup: 'Arbitrum' }, { label: NETWORK_LABEL.ARBITRUM_GOERLI, name: NETWORK.ARBITRUM_GOERLI, parentGroup: 'Arbitrum' } ] as INodeOptionsValue[] export const OptimismNetworks = [ { label: NETWORK_LABEL.OPTIMISM, name: NETWORK.OPTIMISM, parentGroup: 'Optimism' }, { label: NETWORK_LABEL.OPTIMISM_GOERLI, name: NETWORK.OPTIMISM_GOERLI, parentGroup: 'Optimism' } ] as INodeOptionsValue[] export const AvalancheNetworks = [ { label: NETWORK_LABEL.AVALANCHE, name: NETWORK.AVALANCHE, parentGroup: 'Avalanche' }, { label: NETWORK_LABEL.AVALANCHE_TESTNET, name: NETWORK.AVALANCHE_TESTNET, parentGroup: 'Avalanche' } ] as INodeOptionsValue[] export const SolanaNetworks = [ { label: NETWORK_LABEL.SOLANA, name: NETWORK.SOLANA, parentGroup: 'Solana' }, { label: NETWORK_LABEL.SOLANA_DEVNET, name: NETWORK.SOLANA_DEVNET, parentGroup: 'Solana' }, { label: NETWORK_LABEL.SOLANA_TESTNET, name: NETWORK.SOLANA_TESTNET, parentGroup: 'Solana' } ] as INodeOptionsValue[] export const FantomNetworks = [ { label: NETWORK_LABEL.FANTOM, name: NETWORK.FANTOM, parentGroup: 'Fantom' }, { label: NETWORK_LABEL.FANTOM_TESTNET, name: NETWORK.FANTOM_TESTNET, parentGroup: 'Fantom' } ] as INodeOptionsValue[] export const GnosisNetworks = [ { label: NETWORK_LABEL.GNOSIS, name: NETWORK.GNOSIS, parentGroup: 'Gnosis' } ] as INodeOptionsValue[] export const HecoNetworks = [ { label: NETWORK_LABEL.HECO, name: NETWORK.HECO, parentGroup: 'Heco' } ] as INodeOptionsValue[] export const HarmonyNetworks = [ { label: NETWORK_LABEL.HARMONY, name: NETWORK.HARMONY, parentGroup: 'Harmony' } ] as INodeOptionsValue[] export const MoonRiverNetworks = [ { label: NETWORK_LABEL.MOONRIVER, name: NETWORK.MOONRIVER, parentGroup: 'MoonRiver' } ] as INodeOptionsValue[] export const MoonBeamNetworks = [ { label: NETWORK_LABEL.MOONBEAM, name: NETWORK.MOONBEAM, parentGroup: 'MoonBeam' } ] as INodeOptionsValue[] export const MetisNetworks = [ { label: NETWORK_LABEL.METIS, name: NETWORK.METIS, parentGroup: 'Metis' } ] as INodeOptionsValue[] export const KlatynNetworks = [ { label: NETWORK_LABEL.KLATYN_TESTNET, name: NETWORK.KLATYN_TESTNET, parentGroup: 'Klatyn' } ] as INodeOptionsValue[] export const FLOWNetworks = [ { label: NETWORK_LABEL.FLOW, name: NETWORK.FLOW, parentGroup: 'Flow Mainnet' }, { label: NETWORK_LABEL.FLOW_TESTNET, name: NETWORK.FLOW_TESTNET, parentGroup: 'Flow Testnet' } ] as INodeOptionsValue[] /** * Network Providers */ export const customNetworkProviders = [ { label: 'Custom RPC Endpoint', name: NETWORK_PROVIDER.CUSTOMRPC, description: 'HTTP endpoint', parentGroup: 'Custom Nodes' }, { label: 'Custom Websocket Endpoint', name: NETWORK_PROVIDER.CUSTOMWSS, description: 'WSS Endpoint', parentGroup: 'Custom Nodes' } ] as INodeOptionsValue[] export const infuraNetworkProviders = [ { label: 'Infura', name: NETWORK_PROVIDER.INFURA, description: 'Infura RPC/Websocket', parentGroup: 'Private Nodes' } ] as INodeOptionsValue[] export const alchemyNetworkProviders = [ { label: 'Alchemy', name: NETWORK_PROVIDER.ALCHEMY, description: 'Alchemy RPC/Websocket', parentGroup: 'Private Nodes' } ] as INodeOptionsValue[] export const quickNodeNetworkProviders = [ { label: 'QuickNode', name: NETWORK_PROVIDER.QUICKNODE, description: 'QuickNode HTTP and WSS Endpoints', parentGroup: 'Private Nodes' } ] as INodeOptionsValue[] export const ethTestNetworkProviders = [ ...alchemyNetworkProviders, ...infuraNetworkProviders, ...quickNodeNetworkProviders, ...customNetworkProviders ] as INodeOptionsValue[] export const ethNetworkProviders = [ { label: 'Cloudfare', name: NETWORK_PROVIDER.CLOUDFARE, description: 'Public Cloudfare RPC', parentGroup: 'Public Nodes' }, ...alchemyNetworkProviders, ...infuraNetworkProviders, ...quickNodeNetworkProviders, ...customNetworkProviders ] as INodeOptionsValue[] export const polygonNetworkProviders = [ { label: 'Polygon', name: NETWORK_PROVIDER.POLYGON, description: 'Public Polygon RPC/Websocket', parentGroup: 'Public Nodes' }, ...alchemyNetworkProviders, ...infuraNetworkProviders, ...quickNodeNetworkProviders, ...customNetworkProviders ] as INodeOptionsValue[] export const binanceNetworkProviders = [ { label: 'Binance', name: NETWORK_PROVIDER.BINANCE, description: 'Public Binance RPC/Websocket', parentGroup: 'Public Nodes' }, ...quickNodeNetworkProviders, ...customNetworkProviders ] as INodeOptionsValue[] export const avalancheNetworkProviders = [ { label: 'Avalanche', name: NETWORK_PROVIDER.AVAX, description: 'Public Avalanche RPC/Websocket', parentGroup: 'Public Nodes' }, ...infuraNetworkProviders, ...quickNodeNetworkProviders, ...customNetworkProviders ] as INodeOptionsValue[] export const fantomNetworkProviders = [ { label: 'Fantom', name: NETWORK_PROVIDER.FANTOM, description: 'Public Fantom RPC/Websocket', parentGroup: 'Public Nodes' }, ...quickNodeNetworkProviders, ...customNetworkProviders ] as INodeOptionsValue[] export const gnosisNetworkProviders = [ { label: 'Gnosis', name: NETWORK_PROVIDER.GNOSIS, description: 'Public Gnosis RPC/Websocket', parentGroup: 'Public Nodes' }, ...quickNodeNetworkProviders, ...customNetworkProviders ] as INodeOptionsValue[] export const hecoNetworkProviders = [ { label: 'Huobi ECO Chain', name: NETWORK_PROVIDER.HECO, description: 'Public HECO RPC/Websocket', parentGroup: 'Public Nodes' }, ...customNetworkProviders ] as INodeOptionsValue[] export const solanaNetworkProviders = [...quickNodeNetworkProviders, ...customNetworkProviders] as INodeOptionsValue[] export const harmonyNetworkProviders = [ { label: 'Harmony', name: NETWORK_PROVIDER.HARMONY, description: 'Public Harmony RPC/Websocket', parentGroup: 'Public Nodes' }, ...quickNodeNetworkProviders, ...customNetworkProviders ] as INodeOptionsValue[] export const moonriverNetworkProviders = [ { label: 'Moonriver', name: NETWORK_PROVIDER.MOONRIVER, description: 'Public Moonriver RPC/Websocket', parentGroup: 'Public Nodes' }, ...customNetworkProviders ] as INodeOptionsValue[] export const moonbeamNetworkProviders = [ { label: 'Moonbeam', name: NETWORK_PROVIDER.MOONBEAM, description: 'Public Moonbeam RPC/Websocket', parentGroup: 'Public Nodes' }, ...customNetworkProviders ] as INodeOptionsValue[] export const metisNetworkProviders = [ { label: 'Metis', name: NETWORK_PROVIDER.METIS, description: 'Public Metis RPC/Websocket', parentGroup: 'Public Nodes' }, ...customNetworkProviders ] as INodeOptionsValue[] export const klaytnNetworkProviders = [ { label: 'Klaytn', name: NETWORK_PROVIDER.KLAYTN, description: 'Public Klaytn RPC/Websocket', parentGroup: 'Public Nodes' }, ...customNetworkProviders ] as INodeOptionsValue[] export const flowNetworkProviders = [ { label: 'Flow', name: NETWORK_PROVIDER.FLOW, description: 'Public FLOW RPC/Websocket', parentGroup: 'Public Nodes' }, ...customNetworkProviders ] as INodeOptionsValue[] export const flowTestnetNetworkProviders = [ { label: 'Flow Testnet', name: NETWORK_PROVIDER.FLOW_TESTNET, description: 'Public FLOW testnet RPC/Websocket', parentGroup: 'Public Nodes' }, ...customNetworkProviders ] as INodeOptionsValue[] export function getCustomRPCProvider(jsonRPC: string) { return new ethers.providers.JsonRpcProvider(jsonRPC) } export function getCustomWebsocketProvider(websocketRPC: string) { return new ethers.providers.WebSocketProvider(websocketRPC) } export async function getBscMainnetProvider() { return await getFallbackProvider(binanceMainnetRPC, 'binance', CHAIN_ID.BINANCE_MAINNET) } export async function getBscTestnetProvider() { return await new ethers.providers.JsonRpcProvider(binanceTestnetRPC[0]) } export async function getPolygonMainnetProvider() { return await getFallbackProvider(polygonMainnetRPC, 'polygon', CHAIN_ID.MATIC) } export async function getPolygonTestnetProvider() { return await getFallbackProvider(polygonMumbaiRPC, 'polygon', CHAIN_ID.MATIC_MUMBAI) } export async function getAvalancheTestnetProvider() { return await getFallbackProvider(avalancheTestnetRPC, 'avalanche', CHAIN_ID.AVALANCHE_TESTNET) } export async function getAvalancheMainnetProvider() { return await getFallbackProvider(avalancheMainnetRPC, 'avalanche', CHAIN_ID.AVALANCHE_MAINNET) } export async function getGnosisMainnetProvider() { return new ethers.providers.JsonRpcProvider(gnosisMainnetRPC[0]) } export async function getHecoMainnetProvider() { return new ethers.providers.JsonRpcProvider(hecoMainnetRPC[0]) } export async function getFantomMainnetProvider() { return new ethers.providers.JsonRpcProvider(fantomMainnetRPC[0]) } export async function getFantomTestnetProvider() { return new ethers.providers.JsonRpcProvider(fantomTestnetRPC[0]) } export async function getHarmonyMainnetProvider() { return await getFallbackProvider(harmoneyMainnetRPC, 'harmony', CHAIN_ID.HARMONY) } export async function getMoonriverMainnetProvider() { return new ethers.providers.JsonRpcProvider(moonriverMainnetRPC[0]) } export async function getMoonbeamMainnetProvider() { return new ethers.providers.JsonRpcProvider(moonbeamMainnetRPC[0]) } export async function getMetisMainnetProvider() { return new ethers.providers.JsonRpcProvider(metisMainnetRPC[0]) } export async function getKlaytnTestnetProvider() { return new ethers.providers.JsonRpcProvider(klaytnTestnetRPC[0]) } export async function getFallbackProvider(rpcs: string[], network: string, chainId: CHAIN_ID) { const prvs = [] for (let i = 0; i < rpcs.length; i++) { const node = rpcs[i] const prv = new ethers.providers.StaticJsonRpcProvider( { url: node, timeout: 1000 }, { name: network, chainId } ) await prv.ready prvs.push({ provider: prv, stallTimeout: 1000 }) } return new ethers.providers.FallbackProvider(prvs) } export async function getNetworkProvider( networkProvider: NETWORK_PROVIDER, network: NETWORK, credentials: ICommonObject | undefined, jsonRPC?: string, websocketRPC?: string, isWebSocket?: boolean ): Promise { if ( credentials === undefined && (networkProvider === NETWORK_PROVIDER.INFURA || networkProvider === NETWORK_PROVIDER.ALCHEMY || networkProvider === NETWORK_PROVIDER.QUICKNODE) ) { throw new Error('Missing credentials') } switch (networkProvider) { case NETWORK_PROVIDER.ALCHEMY: return isWebSocket ? new ethers.providers.WebSocketProvider(`${alchemyWSSAPIs[network]}${credentials!.apiKey}`) : new ethers.providers.AlchemyProvider(network, credentials!.apiKey) case NETWORK_PROVIDER.INFURA: return isWebSocket ? new ethers.providers.WebSocketProvider(`${infuraWSSAPIs[network]}${credentials!.apiKey}`) : new ethers.providers.InfuraProvider(network, { apiKey: credentials!.apiKey, secretKey: credentials!.secretKey }) case NETWORK_PROVIDER.QUICKNODE: return isWebSocket ? new ethers.providers.WebSocketProvider(credentials!.wssProvider as string) : new ethers.providers.JsonRpcProvider(credentials!.httpProvider as string) case NETWORK_PROVIDER.CLOUDFARE: return new ethers.providers.CloudflareProvider() case NETWORK_PROVIDER.BINANCE: if (network === NETWORK.BSC) return await getBscMainnetProvider() else if (network === NETWORK.BSC_TESTNET) return await getBscTestnetProvider() else return null case NETWORK_PROVIDER.POLYGON: if (network === NETWORK.MATIC) return await getPolygonMainnetProvider() else if (network === NETWORK.MATIC_MUMBAI) return await getPolygonTestnetProvider() else return null case NETWORK_PROVIDER.AVAX: if (network === NETWORK.AVALANCHE) return await getAvalancheMainnetProvider() else if (network === NETWORK.AVALANCHE_TESTNET) return await getAvalancheTestnetProvider() else return null case NETWORK_PROVIDER.FANTOM: if (network === NETWORK.FANTOM) return await getFantomMainnetProvider() else if (network === NETWORK.FANTOM_TESTNET) return await getFantomTestnetProvider() else return null case NETWORK_PROVIDER.GNOSIS: return await getGnosisMainnetProvider() case NETWORK_PROVIDER.HECO: return await getHecoMainnetProvider() case NETWORK_PROVIDER.HARMONY: return await getHarmonyMainnetProvider() case NETWORK_PROVIDER.MOONRIVER: return await getMoonriverMainnetProvider() case NETWORK_PROVIDER.MOONBEAM: return await getMoonbeamMainnetProvider() case NETWORK_PROVIDER.METIS: return await getMetisMainnetProvider() case NETWORK_PROVIDER.KLAYTN: return await getKlaytnTestnetProvider() case NETWORK_PROVIDER.CUSTOMRPC: return jsonRPC ? getCustomRPCProvider(jsonRPC) : null case NETWORK_PROVIDER.CUSTOMWSS: return websocketRPC ? getCustomWebsocketProvider(websocketRPC) : null default: return null } } export function getNetworkProvidersList(network: NETWORK): INodeOptionsValue[] { switch (network) { case NETWORK.MAINNET: return ethNetworkProviders case NETWORK.GÖRLI: return ethTestNetworkProviders case NETWORK.MATIC: case NETWORK.MATIC_MUMBAI: return polygonNetworkProviders case NETWORK.OPTIMISM: case NETWORK.OPTIMISM_GOERLI: return ethTestNetworkProviders case NETWORK.ARBITRUM: case NETWORK.ARBITRUM_GOERLI: return ethTestNetworkProviders case NETWORK.AVALANCHE: case NETWORK.AVALANCHE_TESTNET: return avalancheNetworkProviders case NETWORK.FANTOM: case NETWORK.FANTOM_TESTNET: return fantomNetworkProviders case NETWORK.SOLANA: case NETWORK.SOLANA_DEVNET: case NETWORK.SOLANA_TESTNET: return solanaNetworkProviders case NETWORK.BSC: case NETWORK.BSC_TESTNET: return binanceNetworkProviders case NETWORK.GNOSIS: return gnosisNetworkProviders case NETWORK.HECO: return hecoNetworkProviders case NETWORK.HARMONY: return harmonyNetworkProviders case NETWORK.MOONRIVER: return moonriverNetworkProviders case NETWORK.MOONBEAM: return moonbeamNetworkProviders case NETWORK.METIS: return metisNetworkProviders case NETWORK.KLATYN_TESTNET: return klaytnNetworkProviders case NETWORK.FLOW: return flowNetworkProviders case NETWORK.FLOW_TESTNET: return flowTestnetNetworkProviders default: return customNetworkProviders } } export const networkProviderCredentials = [ { label: 'Credential Method', name: 'credentialMethod', type: 'options', options: [ { label: 'Alchemy API Key', name: 'alchemyApi', show: { 'networks.networkProvider': [NETWORK_PROVIDER.ALCHEMY] } }, { label: 'Infura API Key', name: 'infuraApi', show: { 'networks.networkProvider': [NETWORK_PROVIDER.INFURA] } }, { label: 'QuickNode Endpoints', name: 'quickNodeEndpoints', show: { 'networks.networkProvider': [NETWORK_PROVIDER.QUICKNODE] } } ], show: { 'networks.networkProvider': [NETWORK_PROVIDER.ALCHEMY, NETWORK_PROVIDER.INFURA, NETWORK_PROVIDER.QUICKNODE] } } ] /** * URLs */ export const etherscanAPIs = { [NETWORK.MAINNET]: 'https://api.etherscan.io/api', [NETWORK.GÖRLI]: 'https://api-goerli.etherscan.io/api', [NETWORK.MATIC]: 'https://api.polygonscan.com/api', [NETWORK.MATIC_MUMBAI]: 'https://api-testnet.polygonscan.com/api', [NETWORK.OPTIMISM]: 'https://api-optimistic.etherscan.io/api', [NETWORK.OPTIMISM_GOERLI]: 'https://api-goerli-optimistic.etherscan.io/api', [NETWORK.ARBITRUM]: 'https://api.arbiscan.io/api', [NETWORK.ARBITRUM_GOERLI]: 'https://api-goerli.arbiscan.io/api', [NETWORK.BSC]: 'https://api.bscscan.com/api', [NETWORK.BSC_TESTNET]: 'https://api-testnet.bscscan.com/api', [NETWORK.AVALANCHE]: 'https://api.snowtrace.io/api', [NETWORK.AVALANCHE_TESTNET]: 'https://api-testnet.snowtrace.io/api', [NETWORK.FANTOM]: 'https://api.ftmscan.com/api', [NETWORK.FANTOM_TESTNET]: 'https://api-testnet.ftmscan.com/api', [NETWORK.CRONOS]: 'https://api.cronoscan.com/api', [NETWORK.CRONOS_TESTNET]: 'https://api-testnet.cronoscan.com/api', [NETWORK.GNOSIS]: 'https://api.gnosisscan.io/api', [NETWORK.CELO]: 'https://api.celoscan.io/api', [NETWORK.MOONRIVER]: 'https://api-moonriver.moonscan.io/api', [NETWORK.MOONBEAM]: 'https://api-moonbeam.moonscan.io/api' } as INetworkMapping export const infuraHTTPAPIs = { [NETWORK.MAINNET]: 'https://mainnet.infura.io/v3/', [NETWORK.GÖRLI]: 'https://goerli.infura.io/v3/', [NETWORK.MATIC]: 'https://polygon-mainnet.infura.io/v3/', [NETWORK.MATIC_MUMBAI]: 'https://polygon-mumbai.infura.io/v3/', [NETWORK.OPTIMISM]: 'https://optimism-mainnet.infura.io/v3/', [NETWORK.OPTIMISM_GOERLI]: 'https://optimism-goerli.infura.io/v3/', [NETWORK.ARBITRUM]: 'https://arbitrum-mainnet.infura.io/v3/', [NETWORK.ARBITRUM_GOERLI]: 'https://arbitrum-goerli.infura.io/v3/' } as INetworkMapping export const infuraWSSAPIs = { [NETWORK.MAINNET]: 'wss://mainnet.infura.io/ws/v3/', [NETWORK.GÖRLI]: 'wss://goerli.infura.io/ws/v3/', [NETWORK.MATIC]: 'wss://polygon-mainnet.infura.io/ws/v3/', [NETWORK.MATIC_MUMBAI]: 'wss://polygon-mumbai.infura.io/ws/v3/', [NETWORK.OPTIMISM]: 'wss://optimism-mainnet.infura.io/ws/v3/', [NETWORK.OPTIMISM_GOERLI]: 'wss://optimism-goerli.infura.io/ws/v3/', [NETWORK.ARBITRUM]: 'wss://arbitrum-mainnet.infura.io/ws/v3/', [NETWORK.ARBITRUM_GOERLI]: 'wss://arbitrum-goerli.infura.io/ws/v3/' } as INetworkMapping export const alchemyHTTPAPIs = { [NETWORK.MAINNET]: 'https://eth-mainnet.alchemyapi.io/v2/', [NETWORK.GÖRLI]: 'https://eth-goerli.alchemyapi.io/v2/', [NETWORK.MATIC]: 'https://polygon-mainnet.g.alchemy.com/v2/', [NETWORK.MATIC_MUMBAI]: 'https://polygon-mumbai.g.alchemy.com/v2/', [NETWORK.OPTIMISM]: 'https://opt-mainnet.g.alchemy.com/v2/', [NETWORK.OPTIMISM_GOERLI]: 'https://opt-goerli.g.alchemy.com/v2/', [NETWORK.ARBITRUM]: 'https://arb-mainnet.g.alchemy.com/v2/', [NETWORK.ARBITRUM_GOERLI]: 'https://arb-goerli.g.alchemy.com/v2/', [NETWORK.SOLANA]: 'https://solana-mainnet.g.alchemy.com/v2/', [NETWORK.SOLANA_DEVNET]: 'https://solana-devnet.g.alchemy.com/v2/' } as INetworkMapping export const alchemyWSSAPIs = { [NETWORK.MAINNET]: 'wss://eth-mainnet.alchemyapi.io/v2/', [NETWORK.GÖRLI]: 'wss://eth-goerli.alchemyapi.io/v2/', [NETWORK.MATIC]: 'wss://polygon-mainnet.g.alchemy.com/v2/', [NETWORK.MATIC_MUMBAI]: 'wss://polygon-mumbai.g.alchemy.com/v2/', [NETWORK.OPTIMISM]: 'wss://opt-mainnet.g.alchemy.com/v2/', [NETWORK.OPTIMISM_GOERLI]: 'wss://opt-goerli.g.alchemy.com/v2/', [NETWORK.ARBITRUM]: 'wss://arb-mainnet.g.alchemy.com/v2/', [NETWORK.ARBITRUM_GOERLI]: 'wss://arb-goerli.g.alchemy.com/v2/', [NETWORK.SOLANA]: 'wss://solana-mainnet.g.alchemy.com/v2/', [NETWORK.SOLANA_DEVNET]: 'wss://solana-devnet.g.alchemy.com/v2/' } as INetworkMapping export const networkExplorers = { [NETWORK.MAINNET]: 'https://etherscan.io', [NETWORK.GÖRLI]: 'https://goerli.etherscan.io', [NETWORK.MATIC]: 'https://polygonscan.com', [NETWORK.MATIC_MUMBAI]: 'https://mumbai.polygonscan.com', [NETWORK.OPTIMISM]: 'https://optimistic.etherscan.io', [NETWORK.OPTIMISM_GOERLI]: 'https://goerli-optimistic.etherscan.io', [NETWORK.ARBITRUM]: 'https://arbiscan.io', [NETWORK.ARBITRUM_GOERLI]: 'https://goerli-explorer.arbitrum.io', [NETWORK.BSC]: 'https://bscscan.com', [NETWORK.BSC_TESTNET]: 'https://testnet.bscscan.com', [NETWORK.FANTOM]: 'https://ftmscan.com', [NETWORK.FANTOM_TESTNET]: 'https://testnet.ftmscan.com', [NETWORK.CRONOS]: 'https://cronoscan.com', [NETWORK.CRONOS_TESTNET]: 'https://testnet.cronoscan.com', [NETWORK.GNOSIS]: 'https://gnosisscan.io', [NETWORK.CELO]: 'https://celoscan.io', [NETWORK.MOONRIVER]: 'https://moonriver.moonscan.io', [NETWORK.MOONBEAM]: 'https://moonscan.io' } as INetworkMapping export const openseaExplorers = { [NETWORK.MAINNET]: 'https://opensea.io', [NETWORK.GÖRLI]: 'https://testnets.opensea.io', [NETWORK.MATIC]: 'https://opensea.io/assets/matic', [NETWORK.MATIC_MUMBAI]: 'https://testnets.opensea.io/assets/mumbai' } as INetworkMapping export const binanceTestnetRPC = [ 'https://data-seed-prebsc-1-s3.binance.org:8545', 'https://data-seed-prebsc-1-s1.binance.org:8545', 'https://data-seed-prebsc-2-s2.binance.org:8545' ] as string[] export const binanceMainnetRPC = [ 'https://bsc-dataseed1.ninicoin.io', 'https://bsc-dataseed1.defibit.io', 'https://bsc-dataseed.binance.org', 'https://bsc.nodereal.io' ] as string[] export const polygonMumbaiRPC = [ 'https://matic-testnet-archive-rpc.bwarelabs.com', 'https://rpc-mumbai.maticvigil.com', 'https://matic-mumbai.chainstacklabs.com', 'https://rpc-mumbai.matic.today' ] as string[] export const polygonMainnetRPC = ['https://polygon-rpc.com', 'https://rpc-mainnet.matic.quiknode.pro'] as string[] export const avalancheMainnetRPC = ['https://api.avax.network/ext/bc/C/rpc', 'https://rpc.ankr.com/avalanche'] as string[] export const avalancheTestnetRPC = ['https://api.avax-test.network/ext/bc/C/rpc'] as string[] export const gnosisMainnetRPC = ['https://rpc.gnosischain.com'] as string[] export const hecoMainnetRPC = ['https://http-mainnet.hecochain.com'] as string[] export const fantomMainnetRPC = ['https://rpc.ftm.tools'] as string[] export const fantomTestnetRPC = ['https://rpc.testnet.fantom.network'] as string[] export const solanaMainnetRPC = ['https://api.mainnet-beta.solana.com'] as string[] export const solanaDevnetRPC = ['https://api.devnet.solana.com'] as string[] export const solanaTestnetRPC = ['https://api.testnet.solana.com'] as string[] export const harmoneyMainnetRPC = ['https://api.harmony.one', 'https://harmony-mainnet.chainstacklabs.com'] as string[] export const moonriverMainnetRPC = ['https://rpc.api.moonriver.moonbeam.network'] as string[] export const moonbeamMainnetRPC = ['https://rpc.api.moonbeam.network'] as string[] export const metisMainnetRPC = ['https://andromeda.metis.io/?owner=1088'] as string[] export const klaytnTestnetRPC = ['https://api.baobab.klaytn.net:8651 '] as string[] export const chainIdLookup = { [NETWORK.MAINNET]: CHAIN_ID.MAINNET, [NETWORK.GÖRLI]: CHAIN_ID.GÖRLI, [NETWORK.MATIC]: CHAIN_ID.MATIC, [NETWORK.MATIC_MUMBAI]: CHAIN_ID.MATIC_MUMBAI, [NETWORK.OPTIMISM]: CHAIN_ID.OPT_MAINNET, [NETWORK.OPTIMISM_GOERLI]: CHAIN_ID.OPT_TESTNET_GOERLI, [NETWORK.ARBITRUM]: CHAIN_ID.ARB_MAINNET, [NETWORK.ARBITRUM_GOERLI]: CHAIN_ID.ARB_TESTNET_GOERLI, [NETWORK.ARBITRUM_NOVA]: CHAIN_ID.ARB_NOVA, [NETWORK.BSC]: CHAIN_ID.BINANCE_MAINNET, [NETWORK.BSC_TESTNET]: CHAIN_ID.BINANCE_TESTNET, [NETWORK.CRONOS]: CHAIN_ID.CRONOS_MAINNET, [NETWORK.CRONOS_TESTNET]: CHAIN_ID.CRONOS_TESTNET, [NETWORK.AVALANCHE]: CHAIN_ID.AVALANCHE_MAINNET, [NETWORK.AVALANCHE_TESTNET]: CHAIN_ID.AVALANCHE_TESTNET, [NETWORK.FANTOM]: CHAIN_ID.FANTOM_MAINNET, [NETWORK.FANTOM_TESTNET]: CHAIN_ID.FANTOM_TESTNET, [NETWORK.GNOSIS]: CHAIN_ID.GNOSIS, [NETWORK.CELO]: CHAIN_ID.CELO, [NETWORK.HECO]: CHAIN_ID.HECO, [NETWORK.MOONRIVER]: CHAIN_ID.MOONRIVER, [NETWORK.HECO]: CHAIN_ID.HECO, [NETWORK.HARMONY]: CHAIN_ID.HARMONY, [NETWORK.METIS]: CHAIN_ID.METIS } as INetworkMapping export const domainIdLookup = { [NETWORK.MAINNET]: DOMAIN_ID.MAINNET, [NETWORK.GÖRLI]: DOMAIN_ID.GÖRLI, [NETWORK.MATIC_MUMBAI]: DOMAIN_ID.MATIC_MUMBAI } as INetworkMapping export const nativeCurrency = { [NETWORK.MAINNET]: 'ETH', [NETWORK.GÖRLI]: 'ETH', [NETWORK.MATIC]: 'MATIC', [NETWORK.MATIC_MUMBAI]: 'MATIC', [NETWORK.OPTIMISM]: 'ETH', [NETWORK.OPTIMISM_GOERLI]: 'ETH', [NETWORK.ARBITRUM]: 'ETH', [NETWORK.ARBITRUM_GOERLI]: 'ETH', [NETWORK.ARBITRUM_NOVA]: 'ETH', [NETWORK.BSC]: 'BNB', [NETWORK.BSC_TESTNET]: 'BNB', [NETWORK.FANTOM]: 'FTM', [NETWORK.FANTOM_TESTNET]: 'FTM', [NETWORK.AVALANCHE]: 'AVAX', [NETWORK.AVALANCHE_TESTNET]: 'AVAX', [NETWORK.GNOSIS]: 'xDAI', [NETWORK.CELO]: 'CELO', [NETWORK.MOONRIVER]: 'MOVR', [NETWORK.MOONBEAM]: 'GLMR' } as INetworkMapping export const eventTransferAbi = ['event Transfer(address indexed from, address indexed to, uint value)'] export const functionTransferAbi = ['function transfer(address to, uint256 amount) external returns (boolean)'] export const erc1155SingleTransferAbi = [ 'event TransferSingle(address indexed operator, address indexed from, address indexed to, uint id, uint value)' ] export const erc1155BatchTransferAbi = [ 'event TransferSingle(address indexed operator, address indexed from, address indexed to, uint[] id, uint[] value)' ] ================================================ FILE: packages/components/src/ETHOperations.ts ================================================ import { ICommonObject } from '.' import { NETWORK, NETWORK_PROVIDER } from './ChainNetwork' export interface IETHOperation { name: string value: string parentGroup?: string description?: string body: ICommonObject method: string providerNetworks: { [key: string]: NETWORK[] } overrideUrl?: string inputParameters?: string exampleParameters?: string outputResponse?: string exampleResponse?: ICommonObject } export const operationCategoryMapping = { retrievingBlocks: 'Retrieving Blocks', evmExecution: 'EVM/Smart Contract Execution', readingTransactions: 'Reading Transactions', accountInformation: 'Account Information', eventLogs: 'Event Logs', chainInformation: 'Chain Information', txReceiptInformation: 'Transaction Receipt Information', retrievingUncles: 'Retrieving Uncles', filters: 'Filters', readWriteTransactions: 'Reading & Writing Transactions', gettingBlocks: 'Getting Blocks', networkInformation: 'Network Information', slotInformation: 'Slot Information', nodeInformation: 'Node Information', tokenInformation: 'Token Information', networkInflation: 'Network Inflation' } as any export const alchemySupportedNetworks = [ NETWORK.MAINNET, NETWORK.GÖRLI, NETWORK.MATIC, NETWORK.MATIC_MUMBAI, NETWORK.OPTIMISM, NETWORK.OPTIMISM_GOERLI, NETWORK.ARBITRUM, NETWORK.ARBITRUM_GOERLI ] export const infuraSupportedNetworks = [...alchemySupportedNetworks, NETWORK.AVALANCHE, NETWORK.AVALANCHE_TESTNET] export const qnSupportedNetworks = [ ...infuraSupportedNetworks, NETWORK.ARBITRUM_NOVA, NETWORK.GNOSIS, NETWORK.BSC, NETWORK.BSC_TESTNET, NETWORK.FANTOM, NETWORK.CELO ] export const ethOperations = [ { name: 'eth_blockNumber', value: 'eth_blockNumber', parentGroup: 'Retrieving Blocks', description: 'Returns the number of the most recent block.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_blockNumber', params: [], id: 0 }, exampleResponse: { jsonrpc: '2.0', id: 0, result: '0xa1c054' } }, { name: 'eth_getBlockByHash', value: 'eth_getBlockByHash', parentGroup: 'Retrieving Blocks', description: 'Returns information about a block by hash.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getBlockByHash', params: [], id: 0 }, inputParameters: `
    • DATA, 32 Bytes - Hash of a block.
    • Boolean - If true it returns the full transaction objects, if false it returns only the hashes of the transactions.
    `, exampleParameters: `[ "0xc0f4906fea23cf6f3cce98cb44e8e1449e455b28d684dfa9ff65426495584de6", true ]`, exampleResponse: { jsonrpc: '2.0', id: 0, result: { difficulty: '0x2d50ba175407', extraData: '0xe4b883e5bda9e7a59ee4bb99e9b1bc', gasLimit: '0x47e7c4', gasUsed: '0x5208', hash: '0xc0f4906fea23cf6f3cce98cb44e8e1449e455b28d684dfa9ff65426495584de6', logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', miner: '0x61c808d82a3ac53231750dadc13c777b59310bd9', mixHash: '0xc38853328f753c455edaa4dfc6f62a435e05061beac136c13dbdcd0ff38e5f40', nonce: '0x3b05c6d5524209f1', number: '0x1e8480', parentHash: '0x57ebf07eb9ed1137d41447020a25e51d30a0c272b5896571499c82c33ecb7288', receiptsRoot: '0x84aea4a7aad5c5899bd5cfc7f309cc379009d30179316a2a7baa4a2ea4a438ac', sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', size: '0x28a', stateRoot: '0x96dbad955b166f5119793815c36f11ffa909859bbfeb64b735cca37cbf10bef1', timestamp: '0x57a1118a', totalDifficulty: '0x262c34a6fd1268f6c', transactions: ['0xc55e2b90168af6972193c1f86fa4d7d7b31a29c156665d15b9cd48618b5177ef'], transactionsRoot: '0xb31f174d27b99cdae8e746bd138a01ce60d8dd7b224f7c60845914def05ecc58', uncles: [] } } }, { name: 'eth_call', value: 'eth_call', parentGroup: 'EVM/Smart Contract Execution', description: 'Executes a new message call immediately without creating a transaction on the block chain.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_call', params: [], id: 1 }, inputParameters: `
    • Object - The transaction call object
      • from: DATA, 20 Bytes - (optional) The address the transaction is sent from.
      • to: DATA, 20 Bytes - The address the transaction is directed to.
      • gas: QUANTITY - (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions.
      • gasPrice: QUANTITY - (optional) Integer of the gasPrice used for each paid gas.
      • value: QUANTITY - (optional) Integer of the value sent with this transaction.
      • data: DATA - (optional) Hash of the method signature and encoded parameters.
    • QUANTITY|TAG - integer block number, or the string "latest", "earliest" or "pending", OR the blockHash (in accordance with EIP-1898)
    `, exampleParameters: `[ { "from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", "gas": "0x76c0", "gasPrice": "0x9184e72a000", "value": "0x9184e72a", "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" }, "latest" ]`, exampleResponse: { jsonrpc: '2.0', id: 1, result: '0x' } }, { name: 'eth_sendRawTransaction', value: 'eth_sendRawTransaction', parentGroup: 'EVM/Smart Contract Execution', description: 'Submits a pre-signed transaction for broadcast to the Ethereum network.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_sendRawTransaction', params: [], id: 1 }, inputParameters: `
    • TRANSACTION DATA - The signed transaction data.
    `, exampleParameters: `[ "0xf869018203e882520894f17f52151ebef6c7334fad080c5704d77216b732881bc16d674ec80000801ba02da1c48b670996dcb1f447ef9ef00b33033c48a4fe938f420bec3e56bfd24071a062e0aa78a81bf0290afbc3a9d8e9a068e6d74caa66c5e0fa8a46deaae96b0833" ]`, exampleResponse: { jsonrpc: '2.0', id: 1, result: '0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331' } }, { name: 'eth_submitWork', value: 'eth_submitWork', parentGroup: 'EVM/Smart Contract Execution', description: 'Used for submitting a proof-of-work solution.', providerNetworks: { [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_submitWork', params: [], id: 1 }, inputParameters: `
    • WORK ARRAY 8 Bytes - The nonce found (64 bits)
    • WORK ARRAY 32 Bytes - The header's pow-hash (256 bits)
    • WORK ARRAY 32 Bytes - The mix digest (256 bits)
    `, exampleParameters: `[ "0x0000000000000001", "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "0xD1FE5700000000000000000000000000D1FE5700000000000000000000000000" ]`, exampleResponse: { jsonrpc: '2.0', id: 1, result: false } }, { name: 'eth_getBlockByNumber', value: 'eth_getBlockByNumber', parentGroup: 'Retrieving Blocks', description: 'Returns information about a block by block number.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getBlockByNumber', params: [], id: 0 }, inputParameters: `
    • QUANTITY|TAG - integer of a block number, or the string "earliest", "latest" or "pending"
    • Boolean - If true it returns the full transaction objects, if false only the hashes of the transactions
    `, exampleParameters: `[ "0x1b4", true ]`, exampleResponse: { jsonrpc: '2.0', id: 0, result: { number: '0x1b4', difficulty: '0x4ea3f27bc', extraData: '0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32', gasLimit: '0x1388', gasUsed: '0x0', hash: '0xdc0818cf78f21a8e70579cb46a43643f78291264dda342ae31049421c82d21ae', logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', miner: '0xbb7b8287f3f0a933474a79eae42cbca977791171', mixHash: '0x4fffe9ae21f1c9e15207b1f472d5bbdd68c9595d461666602f2be20daf5e7843', nonce: '0x689056015818adbe', parentHash: '0xe99e022112df268087ea7eafaf4790497fd21dbeeb6bd7a1721df161a6657a54', receiptsRoot: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', size: '0x220', stateRoot: '0xddc8b0234c2e0cad087c8b389aa7ef01f7d79b2570bccb77ce48648aa61c904d', timestamp: '0x55ba467c', totalDifficulty: '0x78ed983323d', transactions: [], transactionsRoot: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', uncles: [] } } }, { name: 'eth_getTransactionByHash', value: 'eth_getTransactionByHash', parentGroup: 'Reading Transactions', description: 'Returns the information about a transaction requested by transaction hash. In the response object, `blockHash`, `blockNumber`, and `transactionIndex` are `null` when the transaction is pending.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getTransactionByHash', params: [], id: 0 }, inputParameters: `
    • DATA, 32 Bytes - hash of a transaction
    `, exampleParameters: `[ "0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b" ]`, exampleResponse: { jsonrpc: '2.0', id: 0, result: { hash: '0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b', blockHash: '0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2', blockNumber: '0x5daf3b', from: '0xa7d9ddbe1f17865597fbd27ec712455208b6b76d', gas: '0xc350', gasPrice: '0x4a817c800', input: '0x68656c6c6f21', nonce: '0x15', r: '0x1b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5fea', s: '0x4ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c', to: '0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb', transactionIndex: '0x41', v: '0x25', value: '0xf3dbb76162000' } } }, { name: 'eth_getTransactionCount', value: 'eth_getTransactionCount', parentGroup: 'Reading Transactions', description: 'Returns the number of transactions sent from an address.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getTransactionCount', params: [], id: 0 }, inputParameters: `
    • DATA, 20 Bytes - address.
    • QUANTITY|TAG - integer of a block number, or the string "earliest", "latest" or "pending"
    `, exampleParameters: `[ "0xc94770007dda54cF92009BFF0dE90c06F603a09f", "latest" ]`, exampleResponse: { jsonrpc: '2.0', id: 0, result: '0x219' } }, { name: 'eth_getTransactionReceipt', value: 'eth_getTransactionReceipt', parentGroup: 'Reading Transactions', description: 'Returns the receipt of a transaction by transaction hash.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getTransactionReceipt', params: [], id: 0 }, inputParameters: `
    • DATA, 32 Bytes - hash of a transaction.
    `, exampleParameters: `[ "0xab059a62e22e230fe0f56d8555340a29b2e9532360368f810595453f6fdd213b" ]`, exampleResponse: { jsonrpc: '2.0', id: 0, result: { transactionHash: '0xab059a62e22e230fe0f56d8555340a29b2e9532360368f810595453f6fdd213b', blockHash: '0x8243343df08b9751f5ca0c5f8c9c0460d8a9b6351066fae0acbd4d3e776de8bb', blockNumber: '0x429d3b', contractAddress: null, cumulativeGasUsed: '0x64b559', from: '0x00b46c2526e227482e2ebb8f4c69e4674d262e75', gasUsed: '0xcaac', logs: [ { blockHash: '0x8243343df08b9751f5ca0c5f8c9c0460d8a9b6351066fae0acbd4d3e776de8bb', address: '0xb59f67a8bff5d8cd03f6ac17265c550ed8f33907', logIndex: '0x56', data: '0x000000000000000000000000000000000000000000000000000000012a05f200', removed: false, topics: [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', '0x00000000000000000000000000b46c2526e227482e2ebb8f4c69e4674d262e75', '0x00000000000000000000000054a2d42a40f51259dedd1978f6c118a0f0eff078' ], blockNumber: '0x429d3b', transactionIndex: '0xac', transactionHash: '0xab059a62e22e230fe0f56d8555340a29b2e9532360368f810595453f6fdd213b' } ], logsBloom: '0x00000000040000000000000000000000000000000000000000000000000000080000000010000000000000000000000000000000000040000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000010100000000000000000000000000004000000000000200000000000000000000000000000000000000000000', root: '0x3ccba97c7fcc7e1636ce2d44be1a806a8999df26eab80a928205714a878d5114', status: null, to: '0xb59f67a8bff5d8cd03f6ac17265c550ed8f33907', transactionIndex: '0xac' } } }, { name: 'eth_getBlockTransactionCountByHash', value: 'eth_getBlockTransactionCountByHash', parentGroup: 'Reading Transactions', description: 'Returns the number of transactions in a block matching the given block hash.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getBlockTransactionCountByHash', params: [], id: 0 }, inputParameters: `
    • DATA, 32 Bytes - hash of a block.
    `, exampleParameters: `[ "0x8243343df08b9751f5ca0c5f8c9c0460d8a9b6351066fae0acbd4d3e776de8bb" ]`, exampleResponse: { jsonrpc: '2.0', id: 0, result: '0xb0' } }, { name: 'eth_getBlockTransactionCountByNumber', value: 'eth_getBlockTransactionCountByNumber', parentGroup: 'Reading Transactions', description: 'Returns the number of transactions in a block matching the given block number.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getBlockTransactionCountByNumber', params: [], id: 0 }, inputParameters: `
    • QUANTITY|TAG - integer of a block number, or the string "earliest", "latest" or "pending"
    `, exampleParameters: `[ "latest" ]`, exampleResponse: { jsonrpc: '2.0', id: 0, result: '0xee' } }, { name: 'eth_getTransactionByBlockHashAndIndex', value: 'eth_getTransactionByBlockHashAndIndex', parentGroup: 'Reading Transactions', description: 'Returns information about a transaction by block hash and transaction index position.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getTransactionByBlockHashAndIndex', params: [], id: 0 }, inputParameters: `
    • DATA, 32 Bytes - hash of a block.
    • QUANTITY - integer of the transaction index position.
    `, exampleParameters: `[ "0xc0f4906fea23cf6f3cce98cb44e8e1449e455b28d684dfa9ff65426495584de6", "0x0" ]`, exampleResponse: { jsonrpc: '2.0', id: 0, result: { blockHash: '0xc0f4906fea23cf6f3cce98cb44e8e1449e455b28d684dfa9ff65426495584de6', blockNumber: '0x1e8480', from: '0x32be343b94f860124dc4fee278fdcbd38c102d88', gas: '0x51615', gasPrice: '0x6fc23ac00', hash: '0xc55e2b90168af6972193c1f86fa4d7d7b31a29c156665d15b9cd48618b5177ef', input: '0x', nonce: '0x1efc5', to: '0x104994f45d9d697ca104e5704a7b77d7fec3537c', transactionIndex: '0x0', value: '0x821878651a4d70000', v: '0x1b', r: '0x51222d91a379452395d0abaff981af4cfcc242f25cfaf947dea8245a477731f9', s: '0x3a997c910b4701cca5d933fb26064ee5af7fe3236ff0ef2b58aa50b25aff8ca5' } } }, { name: 'eth_getTransactionByBlockNumberAndIndex', value: 'eth_getTransactionByBlockNumberAndIndex', parentGroup: 'Reading Transactions', description: 'Returns information about a transaction by block number and transaction index position.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getTransactionByBlockNumberAndIndex', params: [], id: 0 }, inputParameters: `
    • QUANTITY|TAG - a block number, or the string "earliest", "latest" or "pending"
    • QUANTITY - the transaction index position.
    `, exampleParameters: `[ "latest", "0x0" ]`, exampleResponse: { jsonrpc: '2.0', id: 0, result: { blockHash: '0xf345d6ac2cb6290489915178fef0b2fc7f5a7312dd06773c77532de6740b2b4d', blockNumber: '0xa1c0ff', from: '0x79c384ac9c32e90f00a214c77caac1392ec8cdea', gas: '0xafbe', gasPrice: '0x1270b01800', hash: '0x61b27f711f58ee3090c0976c7e90d2b7eafeb7b889f0db593234f04f8ce07f22', input: '0xa9059cbb0000000000000000000000004b1a99467a284cc690e3237bc69105956816f7620000000000000000000000000000000000000000000000000000001919617600', nonce: '0xa', to: '0xf9e5af7b42d31d51677c75bbbd37c1986ec79aee', transactionIndex: '0x0', value: '0x0', v: '0x1b', r: '0x2123cdbd1130f726ebed55fd2295239778dd4161495d2733f374d7863fc42ab1', s: '0x1f08fd53ff2c3969b88d5e622561d62a799ae68e36f5142689dbfde44bbe1bed' } } }, { name: 'eth_getBalance', value: 'eth_getBalance', parentGroup: 'Account Information', description: 'Returns the balance of the account of a given address.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getBalance', params: [], id: 0 }, inputParameters: `
    • DATA, 20 Bytes - address to check for balance.
    • QUANTITY|TAG - a block number, or the string "earliest", "latest" or "pending"
    `, exampleParameters: `[ "0xc94770007dda54cF92009BFF0dE90c06F603a09f", "latest" ]`, exampleResponse: { jsonrpc: '2.0', id: 0, result: '0x7c2562030800' } }, { name: 'eth_getCode', value: 'eth_getCode', parentGroup: 'Account Information', description: 'Returns code at a given address.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getCode', params: [], id: 0 }, inputParameters: `
    • DATA, 20 Bytes - address
    • QUANTITY|TAG - a block number, or the string "earliest", "latest" or "pending"
    `, exampleParameters: `[ "0xb59f67a8bff5d8cd03f6ac17265c550ed8f33907", "latest" ]`, exampleResponse: { jsonrpc: '2.0', id: 0, result: '0x606060405236156100965763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166306fdde0381146100a557806313af40351461012f57806318160ddd1461014e578063313ce5671461017357806370a082311461019c57806375ad319a146101bb5780638da5cb5b146101ee57806395d89b411461021d578063a9059cbb14610230575b34156100a157600080fd5bfe5b005b34156100b057600080fd5b6100b8610252565b60405160208082528190810183818151815260200191508051906020019080838360005b838110156100f45780820151838201526020016100dc565b50505050905090810190601f1680156101215780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561013a57600080fd5b6100a3600160a060020a0360043516610289565b341561015957600080fd5b61016161030f565b60405190815260200160405180910390f35b341561017e57600080fd5b610186610315565b60405160ff909116815260200160405180910390f35b34156101a757600080fd5b610161600160a060020a036004351661031a565b34156101c657600080fd5b6101da600160a060020a0360043516610335565b604051901515815260200160405180910390f35b34156101f957600080fd5b61020161038e565b604051600160a060020a03909116815260200160405180910390f35b341561022857600080fd5b6100b861039d565b341561023b57600080fd5b6101da600160a060020a03600435166024356103d4565b60408051908101604052601881527f444f5420416c6c6f636174696f6e20496e64696361746f720000000000000000602082015281565b60005433600160a060020a039081169116146102a457600080fd5b600054600160a060020a0380831691167f70aea8d848e8a90fb7661b227dc522eb6395c3dac71b63cb59edd5c9899b236460405160405180910390a36000805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0392909216919091179055565b60015481565b600381565b600160a060020a031660009081526002602052604090205490565b33600160a060020a03811660009081526002602052604081206001015490919060ff16151561036357600080fd5b5050600160a060020a031660009081526002602052604090206001908101805460ff19168217905590565b600054600160a060020a031681565b60408051908101604052600381527f444f540000000000000000000000000000000000000000000000000000000000602082015281565b33600160a060020a03811660009081526002602052604081205490919083908190101561040057600080fd5b33600160a060020a03811660009081526002602052604090206001015460ff16151561042b57600080fd5b85600160a060020a031633600160a060020a03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8760405190815260200160405180910390a3600160a060020a033381166000908152600260205260408082208054899003905591881681522080548601905560019350505050929150505600a165627a7a72305820228dfae3e67abcdc7f73fb3f83a7d23f45acd853774acad9d2e1ac83b940fbe90029' } }, { name: 'eth_getStorageAt', value: 'eth_getStorageAt', parentGroup: 'Account Information', description: "Returns the value from a storage position at a given address, or in other words, returns the state of the contract's storage, which may not be exposed via the contract's methods.", providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getStorageAt', params: [], id: 1 }, inputParameters: `
    • DATA, 20 Bytes - address of the storage.
    • QUANTITY - integer of the position in the storage.
    • QUANTITY|TAG - a block number, or the string "earliest", "latest" or "pending"
    `, exampleParameters: `[ "0x295a70b2de5e3953354a6a8344e616ed314d7251", "0x0", "latest" ]`, exampleResponse: { jsonrpc: '2.0', id: 1, result: '0x00000000000000000000000000000000000000000000000000000000000004d2' } }, { name: 'eth_accounts', value: 'eth_accounts', parentGroup: 'Account Information', description: 'Returns a list of addresses owned by client.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_accounts', params: [], id: 1 }, exampleResponse: { id: 1, jsonrpc: '2.0', result: [] } }, { name: 'parity_nextNonce', value: 'parity_nextNonce', parentGroup: 'Account Information', description: 'Returns next available nonce for transaction from given account. Includes pending block and transaction queue.', providerNetworks: { [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getProof', params: [], id: 1 }, inputParameters: `
    • ADDRESS - a string representing the address (20 bytes) to check for transaction count for
    `, exampleParameters: `[ "0xc94770007dda54cF92009BFF0dE90c06F603a09f" ]`, exampleResponse: { id: 1, jsonrpc: '2.0', result: '0x1a' } }, { name: 'eth_getProof', value: 'eth_getProof', parentGroup: 'Account Information', description: 'Returns the account and storage values of the specified account including the Merkle-proof. This call can be used to verify that the data you are pulling from is not tampered with.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getProof', params: [], id: 1 }, inputParameters: `
    • DATA, 20 Bytes - address of the account.
    • ARRAY, 32 Bytes - array of storage-keys which should be proofed and included.
    • QUANTITY|TAG - a block number, or the string "earliest", "latest" or "pending"
    `, exampleParameters: `[ "0x7F0d15C7FAae65896648C8273B6d7E43f58Fa842", ["0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"], "latest" ]`, exampleResponse: { id: 1, jsonrpc: '2.0', result: { accountProof: [ '0xf90211a...0701bc80', '0xf90211a...0d832380', '0xf90211a...5fb20c80', '0xf90211a...0675b80', '0xf90151a0...ca08080' ], balance: '0x0', codeHash: '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', nonce: '0x0', storageHash: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', storageProof: [ { key: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', proof: ['0xf90211a...0701bc80', '0xf90211a...0d832380'], value: '0x1' } ] } } }, { name: 'eth_getLogs', value: 'eth_getLogs', parentGroup: 'Event Logs', description: 'Returns an array of all logs matching a given filter object.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getLogs', params: [], id: 0 }, inputParameters: `
    • Object - The filter object
      • fromBlock: QUANTITY|TAG - (optional, default: "latest") Value:
        • Integer block number
        • "latest" for the last mined block
        • "pending", "earliest" for not yet mined transactions.
      • toBlock: QUANTITY|TAG - (optional, default: "latest") Value:
        • Integer block number
        • "latest" for the last mined block
        • "pending", "earliest" for not yet mined transactions.
      • address: DATA|Array 20 Bytes - (optional) Contract address or a list of addresses from which logs should originate.
      • topics: Array of DATA - (optional) Array of 32 Bytes DATA topics.
        • Topics are order-dependent. Each topic can also be an array of DATA with "or" options.
      • blockHash: DATA, 32 Bytes - (optional) With the addition of EIP-234 (Geth >= v1.8.13 or Parity >= v2.1.0), blockHash is a new filter option which restricts the logs returned to the single block with the 32-byte hash blockHash. Using blockHash is equivalent to fromBlock = toBlock = the block number with hash blockHash. If blockHash is present in the filter criteria, then neither fromBlock nor toBlock are allowed.
    `, exampleParameters: `[ { "address": "0xb59f67a8bff5d8cd03f6ac17265c550ed8f33907", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" ], "blockHash": "0x8243343df08b9751f5ca0c5f8c9c0460d8a9b6351066fae0acbd4d3e776de8bb" } ]`, exampleResponse: { jsonrpc: '2.0', id: 0, result: [ { address: '0xb59f67a8bff5d8cd03f6ac17265c550ed8f33907', topics: [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', '0x00000000000000000000000000b46c2526e227482e2ebb8f4c69e4674d262e75', '0x00000000000000000000000054a2d42a40f51259dedd1978f6c118a0f0eff078' ], data: '0x000000000000000000000000000000000000000000000000000000012a05f200', blockNumber: '0x429d3b', transactionHash: '0xab059a62e22e230fe0f56d8555340a29b2e9532360368f810595453f6fdd213b', transactionIndex: '0xac', blockHash: '0x8243343df08b9751f5ca0c5f8c9c0460d8a9b6351066fae0acbd4d3e776de8bb', logIndex: '0x56', removed: false } ] } }, { name: 'eth_protocolVersion', value: 'eth_protocolVersion', parentGroup: 'Chain Information', description: 'Returns the current ethereum protocol version.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: [NETWORK.MAINNET, NETWORK.GÖRLI, NETWORK.OPTIMISM, NETWORK.OPTIMISM_GOERLI], [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_protocolVersion', params: [], id: 0 }, exampleResponse: { jsonrpc: '2.0', id: 0, result: '0x40' } }, { name: 'eth_gasPrice', value: 'eth_gasPrice', parentGroup: 'Chain Information', description: 'Returns the current price per gas in wei.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_gasPrice', params: [], id: 0 }, exampleResponse: { jsonrpc: '2.0', id: 0, result: '0x98bca5a00' } }, { name: 'rollup_gasPrices', value: 'rollup_gasPrices', parentGroup: 'Chain Information', description: 'Returns the L1 and L2 gas prices that are being used by the Sequencer to calculate fees.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: [NETWORK.OPTIMISM, NETWORK.OPTIMISM_GOERLI] }, method: 'POST', body: { jsonrpc: '2.0', method: 'rollup_gasPrices', params: [], id: 1 }, exampleResponse: { jsonrpc: '2.0', id: 0, result: { l1gasprice: '0x98bca5a00', l2gasprice: '0x98bca5a00' } } }, { name: 'rollup_getInfo', value: 'rollup_getInfo', parentGroup: 'Chain Information', description: 'Returns useful L2-specific information about the current node.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: [NETWORK.OPTIMISM, NETWORK.OPTIMISM_GOERLI] }, method: 'POST', body: { jsonrpc: '2.0', method: 'rollup_getInfo', params: [], id: 1 }, exampleResponse: { jsonrpc: '2.0', id: 0, result: { mode: 'sequencer', syncing: true, blocknumber: '0x123', timestamp: '189238123123', queueindex: '0x1', index: '0x1', verifiedindex: '0x1' } } }, { name: 'eth_estimateExecutionGas', value: 'eth_estimateExecutionGas', parentGroup: 'Chain Information', description: 'Returns an estimation of gas for a given transaction.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: [NETWORK.OPTIMISM, NETWORK.OPTIMISM_GOERLI] }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_estimateExecutionGas', params: [], id: 1 }, inputParameters: `
    • Object - The transaction call object
      • from: DATA, 20 Bytes - (optional) The address the transaction is sent from.
      • to: DATA, 20 Bytes - The address the transaction is directed to.
      • gas: QUANTITY - (optional) Integer of the gas provided for the transaction execution. eth_estimateGas consumes zero gas, but this parameter may be needed by some executions. NOTE: this parameter has a cap of 550 Million gas per request.
      • gasPrice: QUANTITY - (optional) Integer of the gasPrice used for each paid gas.
      • value: QUANTITY - (optional) Integer of the value sent with this transaction.
      • data: DATA - (optional) Hash of the method signature and encoded parameters.
    • QUANTITY|TAG - integer block number, or the string "latest", "earliest" or "pending"
    `, exampleParameters: `[ { "from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", "gas": "0x76c0", "gasPrice": "0x9184e72a000", "value": "0x9184e72a", "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" }, "latest" ]`, exampleResponse: { jsonrpc: '2.0', id: 0, result: '0x98bca5a00' } }, { name: 'eth_syncing', value: 'eth_syncing', parentGroup: 'Chain Information', description: 'Returns an object with data about the sync status or false.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_syncing', params: [], id: 1 }, exampleResponse: { jsonrpc: '2.0', id: 0, result: false } }, { name: 'eth_mining', value: 'eth_mining', parentGroup: 'Chain Information', description: 'Returns true if the node is mining, otherwise false.', providerNetworks: { [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_mining', params: [], id: 0 }, exampleResponse: { jsonrpc: '2.0', id: 0, result: true } }, { name: 'eth_estimateGas', value: 'eth_estimateGas', parentGroup: 'Chain Information', description: 'Generates and returns an estimate of how much gas is necessary to allow the transaction to complete. The transaction will not be added to the blockchain.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_estimateGas', params: [], id: 0 }, inputParameters: `
    • Object - The transaction call object
      • from: DATA, 20 Bytes - (optional) The address the transaction is sent from.
      • to: DATA, 20 Bytes - The address the transaction is directed to.
      • gas: QUANTITY - (optional) Integer of the gas provided for the transaction execution. eth_estimateGas consumes zero gas, but this parameter may be needed by some executions. NOTE: this parameter has a cap of 550 Million gas per request.
      • gasPrice: QUANTITY - (optional) Integer of the gasPrice used for each paid gas.
      • value: QUANTITY - (optional) Integer of the value sent with this transaction.
      • data: DATA - (optional) Hash of the method signature and encoded parameters.
    • QUANTITY|TAG - integer block number, or the string "latest", "earliest" or "pending"
    `, exampleParameters: `[ { "from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", "gas": "0x76c0", "gasPrice": "0x9184e72a000", "value": "0x9184e72a", "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" }, "latest" ]`, exampleResponse: { id: 1, jsonrpc: '2.0', result: '0x5208' } }, { name: 'eth_feeHistory', value: 'eth_feeHistory', parentGroup: 'Chain Information', description: 'Returns a collection of historical gas information', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: [NETWORK.MAINNET, NETWORK.GÖRLI], [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_feeHistory', params: [], id: 1 }, inputParameters: `
    • Object
      • OLDESTBLOCK - Lowest number block of the returned range.
      • BASEFEEPERGAS - An array of block base fees per gas. This includes the next block after the newest of the returned range, because this value can be derived from the newest block. Zeroes are returned for pre-EIP-1559 blocks.
      • GASUSEDRATIO - An array of block gas used ratios. These are calculated as the ratio of gasUsed and gasLimit.
      • REWARD - (Optional) An array of effective priority fee per gas data points from a single block. All zeroes are returned if the block is empty.
    `, exampleParameters: `[ 4, "latest", [25, 75] ]`, exampleResponse: { id: '1', jsonrpc: '2.0', result: { oldestBlock: 10762137, reward: [ ['0x4a817c7ee', '0x4a817c7ee'], ['0x773593f0', '0x773593f5'], ['0x0', '0x0'], ['0x773593f5', '0x773bae75'] ], baseFeePerGas: ['0x12', '0x10', '0x10', '0xe', '0xd'], gasUsedRatio: [0.026089875, 0.406803, 0, 0.0866665] } } }, { name: 'eth_maxPriorityFeePerGas', value: 'eth_maxPriorityFeePerGas', parentGroup: 'Chain Information', description: 'Returns a fee per gas that is an estimate of how much you can pay as a priority fee, or "tip", to get a transaction included in the current block.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: [NETWORK.MAINNET, NETWORK.GÖRLI], [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_maxPriorityFeePerGas', params: [], id: 1 }, exampleResponse: { id: 1, jsonrpc: '2.0', result: '0x12a05f1f9' } }, { name: 'eth_chainId', value: 'eth_chainId', parentGroup: 'Chain Information', description: 'Returns the currently configured chain ID, a value used in replay-protected transaction signing as introduced by EIP-155.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_chainId', params: [], id: 83 }, exampleResponse: { id: 83, jsonrpc: '2.0', result: '0x3d' // 61 } }, { name: 'net_version', value: 'net_version', parentGroup: 'Chain Information', description: 'Returns the current network id.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'net_version', params: [], id: 67 }, exampleResponse: { id: 67, jsonrpc: '2.0', result: '3' } }, { name: 'net_listening', value: 'net_listening', parentGroup: 'Chain Information', description: 'Returns true if client is actively listening for network connections.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'net_listening', params: [], id: 1 }, exampleResponse: { id: 1, jsonrpc: '2.0', result: true } }, { name: 'net_peerCount', value: 'net_peerCount', parentGroup: 'Chain Information', description: 'Returns the number of peers currently connected to the client.', providerNetworks: { [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'net_peerCount', params: [], id: 1 }, exampleResponse: { id: 1, jsonrpc: '2.0', result: '0x64' } }, { name: 'eth_getUncleByBlockNumberAndIndex', value: 'eth_getUncleByBlockNumberAndIndex', parentGroup: 'Retrieving Uncles', description: 'Returns information about an uncle of a block by number and uncle index position.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getUncleByBlockNumberAndIndex', params: [], id: 0 }, inputParameters: `
    • QUANTITY|TAG - integer of a block number, or the string "earliest", "latest" or "pending"
    • QUANTITY - the uncle's index position.
    `, exampleParameters: `[ "0x29c", "0x0" ]`, exampleResponse: { jsonrpc: '2.0', id: 0, result: { difficulty: '0x57f117f5c', extraData: '0x476574682f76312e302e302f77696e646f77732f676f312e342e32', gasLimit: '0x1388', gasUsed: '0x0', hash: '0x932bdf904546a2287a2c9b2ede37925f698a7657484b172d4e5184f80bdd464d', logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', miner: '0x5bf5e9cf9b456d6591073513de7fd69a9bef04bc', mixHash: '0x4500aa4ee2b3044a155252e35273770edeb2ab6f8cb19ca8e732771484462169', nonce: '0x24732773618192ac', number: '0x299', parentHash: '0xa779859b1ee558258b7008bbabff272280136c5dd3eb3ea3bfa8f6ae03bf91e5', receiptsRoot: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', size: '0x21d', stateRoot: '0x2604fbf5183f5360da249b51f1b9f1e0f315d2ff3ffa1a4143ff221ad9ca1fec', timestamp: '0x55ba4827', transactionsRoot: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', uncles: [] } } }, { name: 'eth_getUncleByBlockHashAndIndex', value: 'eth_getUncleByBlockHashAndIndex', parentGroup: 'Retrieving Uncles', description: 'Returns information about an uncle of a block by hash and uncle index position.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getUncleByBlockHashAndIndex', params: [], id: 0 }, inputParameters: `
    • QUANTITY|TAG - integer of a block number, or the string "earliest", "latest" or "pending"
    • QUANTITY - the uncle's index position.
    `, exampleParameters: `[ "0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35", "0x0" ]`, exampleResponse: { jsonrpc: '2.0', id: 0, result: { difficulty: '0xbf93da424b943', extraData: '0x65746865726d696e652d657539', gasLimit: '0x7a121d', gasUsed: '0x79ea62', hash: '0x824cce7c7c2ec6874b9fa9a9a898eb5f27cbaf3991dfa81084c3af60d1db618c', logsBloom: '0x0948432021200401804810002000000000381001001202440000010020000080a016262050e44850268052000400100505022305a64000054004200b0c04110000080c1055c42001054b804940a0401401008a00112d80082113400c10006580140005011a40220020000010001c0a00082300434002000050840010102082801c2000148540201004491814020480080111a0300600000003800640024200109c00202010044000880000106810a1a010000028a0100000422000140011000050a2a44b3080001060800000540c108102102600d000004730404a880100600021080100403000180000062642408b440060590400080101e046f08000000430', miner: '0xea674fdde714fd979de3edf0f56aa9716b898ec8', mixHash: '0x0b15fe0a9aa789c167b0f5ade7b72969d9f2193014cb4e98382254f60ffb2f4a', nonce: '0xa212d6400b89b3f6', number: '0x5bad54', parentHash: '0x05e19fb68d9ec798073808e8b3170875cb327d4b6cde7d6f60fe194677bb26fd', receiptsRoot: '0x90807b32c4aa4610c57289de57fa68ba50ed53f14dd2c25f1862aa049029dcd6', sha3Uncles: '0xf763576c1ea6a8c61a206e16b1a2451bec5cba1c7545d7ff733a1e8c78715569', size: '0x216', stateRoot: '0xebc7a1603bfffe0a14bdb89f898e2f2824abb40f04579beb7b920c56d6e273c9', timestamp: '0x5b54143f', transactionsRoot: '0x7562cba41e067b364b933e7b566fb2444f6954fef3964a5a487d4cd79d97a56c', uncles: [] } } }, { name: 'eth_getUncleCountByBlockHash', value: 'eth_getUncleCountByBlockHash', parentGroup: 'Retrieving Uncles', description: 'Returns the number of uncles in a block matching the given block hash.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getUncleCountByBlockHash', params: [], id: 0 }, inputParameters: `
    • DATA, 32 Bytes - hash of a block.
    `, exampleParameters: `[ "0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35" ]`, exampleResponse: { jsonrpc: '2.0', id: 0, result: '0x1' } }, { name: 'eth_getUncleCountByBlockNumber', value: 'eth_getUncleCountByBlockNumber', parentGroup: 'Retrieving Uncles', description: 'Returns the number of uncles in a block matching the give block number.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getUncleCountByBlockNumber', params: [], id: 1 }, inputParameters: `
    • QUANTITY|TAG - integer of a block number, or the string "latest", "earliest" or "pending"
    `, exampleParameters: `[ "0xe8" ]`, exampleResponse: { jsonrpc: '2.0', id: 0, result: '0x0' } }, { name: 'eth_getFilterChanges', value: 'eth_getFilterChanges', parentGroup: 'Filters', description: 'Polling method for a filter, which returns an array of logs which occurred since last poll.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getFilterChanges', params: [], id: 73 }, inputParameters: `
    • QUANTITY - the filter id.
    `, exampleParameters: `[ "0xfe704947a3cd3ca12541458a4321c869" ]`, exampleResponse: { jsonrpc: '2.0', id: 73, result: [ { address: '0xb5a5f22694352c15b00323844ad545abb2b11028', blockHash: '0x99e8663c7b6d8bba3c7627a17d774238eae3e793dee30008debb2699666657de', blockNumber: '0x5d12ab', data: '0x0000000000000000000000000000000000000000000000a247d7a2955b61d000', logIndex: '0x0', removed: false, topics: [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', '0x000000000000000000000000bdc0afe57b8e9468aa95396da2ab2063e595f37e', '0x0000000000000000000000007503e090dc2b64a88f034fb45e247cbd82b8741e' ], transactionHash: '0xa74c2432c9cf7dbb875a385a2411fd8f13ca9ec12216864b1a1ead3c99de99cd', transactionIndex: '0x3' }, { address: '0xe38165c9f6deb144afc9c32c206b024817e1496d', blockHash: '0x99e8663c7b6d8bba3c7627a17d774238eae3e793dee30008debb2699666657de', blockNumber: '0x5d12ab', data: '0x0000000000000000000000000000000000000000000000000000000025c6b720', logIndex: '0x3', removed: false, topics: [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', '0x00000000000000000000000080e73e47173b2d00b531bf83bc39e710157125c3', '0x0000000000000000000000008f6cc93795969e5bbbf07c66dfee7d41ad24f1ef' ], transactionHash: '0x9e8f1cb1facb9a11a1cf947634053a0b2d557399f926b12127aa10497a2f0153', transactionIndex: '0x5' } ] } }, { name: 'eth_getFilterLogs', value: 'eth_getFilterLogs', parentGroup: 'Filters', description: 'Returns an array of all logs matching filter with given id.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getFilterLogs', params: [], id: 74 }, inputParameters: `
    • QUANTITY - the filter id.
    `, exampleParameters: `[ "0xfe704947a3cd3ca12541458a4321c869" ]`, exampleResponse: { jsonrpc: '2.0', id: 73, result: [ { address: '0xb5a5f22694352c15b00323844ad545abb2b11028', blockHash: '0x99e8663c7b6d8bba3c7627a17d774238eae3e793dee30008debb2699666657de', blockNumber: '0x5d12ab', data: '0x0000000000000000000000000000000000000000000000a247d7a2955b61d000', logIndex: '0x0', removed: false, topics: [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', '0x000000000000000000000000bdc0afe57b8e9468aa95396da2ab2063e595f37e', '0x0000000000000000000000007503e090dc2b64a88f034fb45e247cbd82b8741e' ], transactionHash: '0xa74c2432c9cf7dbb875a385a2411fd8f13ca9ec12216864b1a1ead3c99de99cd', transactionIndex: '0x3' }, { address: '0xe38165c9f6deb144afc9c32c206b024817e1496d', blockHash: '0x99e8663c7b6d8bba3c7627a17d774238eae3e793dee30008debb2699666657de', blockNumber: '0x5d12ab', data: '0x0000000000000000000000000000000000000000000000000000000025c6b720', logIndex: '0x3', removed: false, topics: [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', '0x00000000000000000000000080e73e47173b2d00b531bf83bc39e710157125c3', '0x0000000000000000000000008f6cc93795969e5bbbf07c66dfee7d41ad24f1ef' ], transactionHash: '0x9e8f1cb1facb9a11a1cf947634053a0b2d557399f926b12127aa10497a2f0153', transactionIndex: '0x5' } ] } }, { name: 'eth_newBlockFilter', value: 'eth_newBlockFilter', parentGroup: 'Filters', description: 'Creates a filter in the node, to notify when a new block arrives.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_newBlockFilter', params: [], id: 73 }, inputParameters: `
    • QUANTITY - the filter id.
    `, exampleParameters: `[ "0xfe704947a3cd3ca12541458a4321c869" ]`, exampleResponse: { jsonrpc: '2.0', id: 0, result: '0x9fb7f13924bb605fd29f3ddd6d193ece' } }, { name: 'eth_newFilter', value: 'eth_newFilter', parentGroup: 'Filters', description: 'Creates a filter object, based on filter options, to notify when the state changes (logs).', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_newFilter', params: [], id: 0 }, inputParameters: `
    • Object - The filter object
      • fromBlock: QUANTITY|TAG - (optional, default: "latest") Value:
        • Integer block number
        • "latest" for the last mined block
        • "pending", "earliest" for not yet mined transactions.
      • toBlock: QUANTITY|TAG - (optional, default: "latest") Value:
        • Integer block number
        • "latest" for the last mined block
        • "pending", "earliest" for not yet mined transactions.
      • address: DATA|Array 20 Bytes - (optional) Contract address or a list of addresses from which logs should originate.
      • topics: Array of DATA - (optional) Array of 32 Bytes DATA topics.
        • Topics are order-dependent. Each topic can also be an array of DATA with "or" options.
    `, exampleParameters: `[ { "fromBlock": "0x1", "toBlock": "0x2", "address": "0xb59f67a8bff5d8cd03f6ac17265c550ed8f33907", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" ] } ]`, exampleResponse: { jsonrpc: '2.0', id: 0, result: '0xdcc9a819f80efa9e1d215a9d41b2d22e' } }, { name: 'eth_newPendingTransactionFilter', value: 'eth_newPendingTransactionFilter', parentGroup: 'Filters', description: 'Creates a filter in the node, to notify when new pending transactions arrive. To check if the state has changed, call eth_getFilterChanges.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks, [NETWORK_PROVIDER.QUICKNODE]: qnSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_newPendingTransactionFilter', params: [], id: 73 }, exampleResponse: { jsonrpc: '2.0', id: 0, result: '0xa08914f1caedfcbe814d9fb33e69678d' } }, { name: 'eth_uninstallFilter', value: 'eth_uninstallFilter', parentGroup: 'Filters', description: 'Uninstalls a filter with given id.', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: alchemySupportedNetworks, [NETWORK_PROVIDER.INFURA]: infuraSupportedNetworks }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_uninstallFilter', params: [], id: 0 }, inputParameters: `
    • QUANTITY - The filter id.
    `, exampleParameters: `["0xfe704947a3cd3ca12541458a4321c869"]`, exampleResponse: { jsonrpc: '2.0', id: 0, result: false } }, { name: 'qn_broadcastRawTransaction', value: 'qn_broadcastRawTransaction', parentGroup: 'Broadcast Transaction', description: 'Sends a transaction to multiple regions to encourage faster mining.', providerNetworks: { [NETWORK_PROVIDER.QUICKNODE]: [NETWORK.MAINNET, NETWORK.GÖRLI, NETWORK.BSC, NETWORK.BSC_TESTNET] }, method: 'POST', body: { jsonrpc: '2.0', method: 'qn_broadcastRawTransaction', params: [], id: 1 }, inputParameters: `
    • DATA - The signed transaction (typically signed with a library, using your private key)
    `, exampleParameters: `["0xf8680685051f4d5c00833d090094ac2d0226ade52e6b4ccc97359359f01e34d50352834c4b40802aa004f4410acfc636f5d65e75ca37448fd0b1e90632661ad3ea071594b646e7621fa0186d78ddbeef43bc372fcc650ad4e77e0629206426cd183d751e9ddcc8d5e777"]`, exampleResponse: { id: 1, jsonrpc: '2.0', result: '0x41ba07c3d8213288869c0c59c3d341074fc63e64359a612a353949de2ed3586a' } } ] as IETHOperation[] export const polygonOperations = [ { name: 'bor_getAuthor', value: 'bor_getAuthor', parentGroup: 'Account Information', description: 'Returns address of Author', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: [NETWORK.MATIC, NETWORK.MATIC_MUMBAI], [NETWORK_PROVIDER.INFURA]: [NETWORK.MATIC, NETWORK.MATIC_MUMBAI] }, method: 'POST', body: { jsonrpc: '2.0', method: 'bor_getAuthor', params: [], id: 1 }, inputParameters: `
    • block number (in hexadecimal format)
    `, exampleParameters: '["0x1234"]', exampleResponse: { jsonrpc: '2.0', id: 1, result: '0x5973918275c01f50555d44e92c9d9b353cadad54' } }, { name: 'bor_getCurrentValidators', value: 'bor_getCurrentValidators', parentGroup: 'Account Information', description: 'Returns current validators', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: [NETWORK.MATIC, NETWORK.MATIC_MUMBAI], [NETWORK_PROVIDER.INFURA]: [NETWORK.MATIC, NETWORK.MATIC_MUMBAI] }, method: 'POST', body: { jsonrpc: '2.0', method: 'bor_getCurrentValidators', params: [], id: 1 }, exampleResponse: { jsonrpc: '2.0', id: 1, result: [ { ID: 0, signer: '0x46a3a41bd932244dd08186e4c19f1a7e48cbcdf4', power: 1, accum: -15 }, { ID: 0, signer: '0x6a654ca3bfb5cfb23bf30bafbf96b3b6ec26bb0e', power: 1, accum: -21 }, { ID: 0, signer: '0x7c7379531b2aee82e4ca06d4175d13b9cbeafd49', power: 5, accum: -8 }, { ID: 0, signer: '0xe77bbfd8ed65720f187efdd109e38d75eaca7385', power: 2, accum: 5 }, { ID: 0, signer: '0xf0245f6251bef9447a08766b9da2b07b28ad80b0', power: 7, accum: -4 } ] } }, { name: 'bor_getCurrentProposer', value: 'bor_getCurrentProposer', parentGroup: 'Account Information', description: "Returns current proposer's address", providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: [NETWORK.MATIC, NETWORK.MATIC_MUMBAI], [NETWORK_PROVIDER.INFURA]: [NETWORK.MATIC, NETWORK.MATIC_MUMBAI] }, method: 'POST', body: { jsonrpc: '2.0', method: 'bor_getCurrentProposer', params: [], id: 1 }, exampleResponse: { jsonrpc: '2.0', id: 1, result: '0xb79fad4ca981472442f53d16365fdf0305ffd8e9' } }, { name: 'bor_getRootHash', value: 'bor_getRootHash', parentGroup: 'Account Information', description: 'Returns the root hash given a block range', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: [NETWORK.MATIC, NETWORK.MATIC_MUMBAI], [NETWORK_PROVIDER.INFURA]: [NETWORK.MATIC, NETWORK.MATIC_MUMBAI] }, method: 'POST', body: { jsonrpc: '2.0', method: 'bor_getRootHash', params: [], id: 1 }, inputParameters: `
    • from block number (in int format)
    • to block number (in int format)
    `, exampleParameters: '[1000000, 1032767]', exampleResponse: { jsonrpc: '2.0', id: 1, result: '04b073e17b7186ab4daae17c5e2cc2d5a729cffd102cede41ee458a2d5573994' } }, { name: 'eth_getSignersAtHash', value: 'eth_getSignersAtHash', parentGroup: 'Account Information', description: 'Returns all signs given a blockhash', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: [NETWORK.MATIC, NETWORK.MATIC_MUMBAI] }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getSignersAtHash', params: [], id: 1 }, inputParameters: `
    • blockhash
    `, exampleParameters: '["0x29fa73e3da83ddac98f527254fe37002e052725a88904bac14f03e919e1e2876"]', exampleResponse: { jsonrpc: '2.0', id: 1, result: [ '0x0375b2fc7140977c9c76d45421564e354ed42277', '0x42eefcda06ead475cde3731b8eb138e88cd0bac3', '0x5973918275c01f50555d44e92c9d9b353cadad54', '0x7fcd58c2d53d980b247f1612fdba93e9a76193e6', '0xb702f1c9154ac9c08da247a8e30ee6f2f3373f41', '0xb8bb158b93c94ed35c1970d610d1e2b34e26652c', '0xf84c74dea96df0ec22e11e7c33996c73fcc2d822' ] } }, { name: 'eth_getTransactionReceiptsByBlock', value: 'eth_getTransactionReceiptsByBlock', parentGroup: 'Reading Transactions', description: 'Returns all transaction receipts for the given block number or hash', providerNetworks: { [NETWORK_PROVIDER.ALCHEMY]: [NETWORK.MATIC, NETWORK.MATIC_MUMBAI] }, method: 'POST', body: { jsonrpc: '2.0', method: 'eth_getTransactionReceiptsByBlock', params: [], id: 1 }, inputParameters: `
    • block_numberin hex OR block_hash
    `, exampleParameters: '["0x989689"]', exampleResponse: { jsonrpc: '2.0', id: 1, result: [ { blockHash: '0x224c0c3153d6bf1f45f461053aee6cbf71e5b5209685c91d81c288b59c82fb47', blockNumber: '0x989689', contractAddress: null, cumulativeGasUsed: '0x63ee1', from: '0x7b5fc677cf27a807adf2ebcef72db3b935df6c0a', gasUsed: '0x63ee1', logs: [ { address: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174', topics: [ '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925', '0x0000000000000000000000003d8a89a20aa73fba0f30d080e8120de9f9555724', '0x000000000000000000000000170f88be2538b8aca98a87c1b186ce5f773cc028' ], data: '0x000000000000000000000000000000000000000000000000000000007dd34dc0', blockNumber: '0x989689', transactionHash: '0x19a2ba50b19ee8ed28fb902fe8cd0a64b0f7a4fe01281e70e2387fdd00ffd5f0', transactionIndex: '0x0', blockHash: '0x224c0c3153d6bf1f45f461053aee6cbf71e5b5209685c91d81c288b59c82fb47', logIndex: '0x0', removed: false }, { address: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174', topics: [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', '0x0000000000000000000000003d8a89a20aa73fba0f30d080e8120de9f9555724', '0x000000000000000000000000170f88be2538b8aca98a87c1b186ce5f773cc028' ], data: '0x000000000000000000000000000000000000000000000000000000007dd34dc0', blockNumber: '0x989689', transactionHash: '0x19a2ba50b19ee8ed28fb902fe8cd0a64b0f7a4fe01281e70e2387fdd00ffd5f0', transactionIndex: '0x0', blockHash: '0x224c0c3153d6bf1f45f461053aee6cbf71e5b5209685c91d81c288b59c82fb47', logIndex: '0x1', removed: false } ], logsBloom: '0x0400000002000000000100000000000000000000000000001000000000104000000000000000000001000020200000001000800000000000001008014020004000000000001000000000000c000200840000000000000000202100000000000000000000020000000001000000000800000000000800000980100010000000002001000000000000000000000000000000100000020000000000000000000000220000000400000000000000008001000008000000000020000000000000004000000002000000000001000000000000000404000600000000100000100020000010008000002000040000000000000010000000000000008000100002100000', status: '0x1', to: '0xd216153c06e857cd7f72665e0af1d7d82172f494', transactionHash: '0x19a2ba50b19ee8ed28fb902fe8cd0a64b0f7a4fe01281e70e2387fdd00ffd5f0', transactionIndex: '0x0' } ] } } ] as IETHOperation[] ================================================ FILE: packages/components/src/Interface.ts ================================================ import { Request } from 'express' import { CronJob } from 'cron' import { ObjectId } from 'mongodb' import { CHAIN_ID, DOMAIN_ID, NETWORK } from '.' /** * Databases */ export interface IWorkflow { _id: ObjectId shortId: string name: string flowData: string deployed: boolean updatedDate: Date createdDate: Date } export interface IExecution { _id: ObjectId shortId: string workflowShortId: string executionData: string state: ExecutionState createdDate: Date stoppedDate?: Date } export interface ICredential { _id: ObjectId name: string nodeCredentialName: string credentialData: string updatedDate: Date createdDate: Date } export interface IWebhook { _id: ObjectId workflowShortId: string webhookEndpoint: string httpMethod: WebhookMethod webhookId: string nodeId: string updatedDate: Date createdDate: Date } export interface IContract { _id: ObjectId name: string abi: string address: string network: string providerCredential: string updatedDate: Date createdDate: Date } export interface IWallet { _id: ObjectId name: string address: string network: string providerCredential: string walletCredential: string updatedDate: Date createdDate: Date } /** * Types */ export type ExecutionState = 'INPROGRESS' | 'FINISHED' | 'ERROR' | 'TERMINATED' | 'TIMEOUT' export type WebhookMethod = 'GET' | 'POST' export type NodeType = 'action' | 'webhook' | 'trigger' export type NodeParamsType = | 'asyncOptions' | 'options' | 'string' | 'number' | 'array' | 'boolean' | 'password' | 'json' | 'code' | 'date' | 'file' | 'folder' export type DbCollectionName = 'Contract' | 'Webhook' | 'Workflow' | 'Credential' | 'Execution' | 'Wallet' export type CommonType = string | number | boolean | undefined | null /** * Others */ export type IDbCollection = { [key in DbCollectionName]: any[] } export interface ICommonObject { [key: string]: CommonType | ICommonObject | CommonType[] | ICommonObject[] } export type INetworkMapping = { [key in NETWORK]: CHAIN_ID | DOMAIN_ID | string } export interface IAttachment { content: string contentType: string size?: number filename?: string } export interface INodeOptionsValue { label: string name: string description?: string parentGroup?: string inputParameters?: string exampleParameters?: string outputResponse?: string exampleResponse?: ICommonObject show?: INodeDisplay hide?: INodeDisplay /* * Only used on credentialMethod option to hide registeredCredentials * For example: noAuth */ hideRegisteredCredential?: boolean } export interface INodeParams { label: string name: string type: NodeParamsType default?: CommonType | ICommonObject | ICommonObject[] description?: string options?: Array array?: Array loadMethod?: string loadFromDbCollections?: DbCollectionName[] optional?: boolean | INodeDisplay show?: INodeDisplay hide?: INodeDisplay rows?: number placeholder?: string } export interface INodeExecutionData { [key: string]: CommonType | CommonType[] | ICommonObject | ICommonObject[] } export interface IWebhookNodeExecutionData { data: INodeExecutionData response?: any } export interface INodeDisplay { [key: string]: string[] | string } export interface INodeProperties { label: string name: string type: NodeType description?: string version: number icon?: string category: string incoming: number outgoing: number } export interface INode extends INodeProperties { actions?: INodeParams[] credentials?: INodeParams[] networks?: INodeParams[] inputParameters?: INodeParams[] loadMethods?: { [key: string]: (nodeData: INodeData, dbCollection?: IDbCollection, apiKeys?: ICommonObject[]) => Promise } webhookMethods?: { createWebhook: (nodeData: INodeData, webhookFullUrl: string) => Promise deleteWebhook: (nodeData: INodeData, webhookId: string) => Promise } run?(nodeData: INodeData): Promise runTrigger?(nodeData: INodeData): Promise removeTrigger?(nodeData: INodeData): Promise runWebhook?(nodeData: INodeData): Promise } export interface INodeData extends INodeProperties { emitEventKey?: string // event emitter key for triggers actions?: ICommonObject credentials?: ICommonObject networks?: ICommonObject inputParameters?: ICommonObject outputResponses?: ICommonObject loadMethod?: string // method to load async options loadFromDbCollections?: DbCollectionName[] // method to load async options req?: Request // For webhook webhookEndpoint?: string // For webhook } export interface INodeCredential { name: string description?: string version: number credentials: INodeParams[] } export interface ICronJobs { [key: string]: CronJob[] } export interface IProviders { [key: string]: { provider: any filter?: any } } export interface IOAuth2RefreshResponse { access_token: string expires_in: string } ================================================ FILE: packages/components/src/abis/WBNB.json ================================================ [ { "constant": true, "inputs": [], "name": "name", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "guy", "type": "address" }, { "name": "wad", "type": "uint256" } ], "name": "approve", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "totalSupply", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "src", "type": "address" }, { "name": "dst", "type": "address" }, { "name": "wad", "type": "uint256" } ], "name": "transferFrom", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "wad", "type": "uint256" } ], "name": "withdraw", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "decimals", "outputs": [ { "name": "", "type": "uint8" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "", "type": "address" } ], "name": "balanceOf", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "symbol", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "dst", "type": "address" }, { "name": "wad", "type": "uint256" } ], "name": "transfer", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [], "name": "deposit", "outputs": [], "payable": true, "stateMutability": "payable", "type": "function" }, { "constant": true, "inputs": [ { "name": "", "type": "address" }, { "name": "", "type": "address" } ], "name": "allowance", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "payable": true, "stateMutability": "payable", "type": "fallback" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "src", "type": "address" }, { "indexed": true, "name": "guy", "type": "address" }, { "indexed": false, "name": "wad", "type": "uint256" } ], "name": "Approval", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "src", "type": "address" }, { "indexed": true, "name": "dst", "type": "address" }, { "indexed": false, "name": "wad", "type": "uint256" } ], "name": "Transfer", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "dst", "type": "address" }, { "indexed": false, "name": "wad", "type": "uint256" } ], "name": "Deposit", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "src", "type": "address" }, { "indexed": false, "name": "wad", "type": "uint256" } ], "name": "Withdrawal", "type": "event" } ] ================================================ FILE: packages/components/src/abis/WETH.json ================================================ [ { "inputs": [ { "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "address", "name": "spender", "type": "address" } ], "inputParameters": [ { "label": "Owner Address", "name": "owner", "type": "string", "show": { "actions.function": ["allowance"] } }, { "label": "Spender Address", "name": "spender", "type": "string", "show": { "actions.function": ["allowance"] } } ], "name": "allowance", "label": "Get Allowance", "description": "Returns the remaining number of tokens that spender will be allowed to spend on behalf of owner through transferFrom. This is zero by default.", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "inputParameters": [ { "label": "Spender Address", "name": "spender", "type": "string", "show": { "actions.function": ["approve"] } }, { "label": "Amount", "name": "amount", "type": "number", "show": { "actions.function": ["approve"] } } ], "name": "approve", "label": "Approve", "description": "Sets amount as the allowance of spender over the caller’s tokens.", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "account", "type": "address" } ], "inputParameters": [ { "label": "Account Address", "name": "account", "type": "string", "description": "Account address to check for remaining amount", "show": { "actions.function": ["balanceOf"] } } ], "name": "balanceOf", "label": "Get Balance", "description": "Returns the amount of tokens owned by account", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "decimals", "label": "Get ERC20 Decimals", "description": "Returns the decimals of ERC20", "outputs": [ { "internalType": "uint8", "name": "", "type": "uint8" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "name", "label": "Get ERC20 Name", "description": "Returns the name of ERC20", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "symbol", "label": "Get ERC20 Symbol", "description": "Returns the symbol of ERC20", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "totalSupply", "label": "Get ERC20 Total Supply", "description": "Returns the total supply of ERC20", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "from", "type": "address" }, { "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "inputParameters": [ { "label": "From Address", "name": "from", "type": "string", "description": "Account address to transfer the token", "show": { "actions.function": ["transferFrom"] } }, { "label": "To Address", "name": "to", "type": "string", "description": "Account address to receive the token", "show": { "actions.function": ["transferFrom"] } }, { "label": "Amount", "name": "amount", "type": "number", "description": "Amount of token transfer", "show": { "actions.function": ["transferFrom"] } } ], "name": "transferFrom", "label": "Transfer From", "description": "Moves amount tokens from sender to recipient using the allowance mechanism. Amount is then deducted from the caller’s allowance.", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" } ] ================================================ FILE: packages/components/src/index.ts ================================================ export * from './Interface' export * from './ETHOperations' export * from './ChainNetwork' export * from './utils' ================================================ FILE: packages/components/src/utils.ts ================================================ import axios, { AxiosRequestConfig } from 'axios' import ClientOAuth2 from 'client-oauth2' import FormData from 'form-data' import { ICommonObject, INodeExecutionData, IWebhookNodeExecutionData, IOAuth2RefreshResponse } from './Interface' import { scryptSync, timingSafeEqual } from 'crypto' import * as fs from 'fs' import * as path from 'path' export const OAUTH2_REFRESHED = 'oAuth2RefreshedData' export const numberOrExpressionRegex = '^(\\d+\\.?\\d*|{{.*}})$' //return true if string consists only numbers OR expression {{}} export const notEmptyRegex = '(.|\\s)*\\S(.|\\s)*' //return true if string is not empty or blank /** * Return responses as INodeExecutionData * * @export * @param {(ICommonObject | ICommonObject[])} responseData * @returns {INodeExecutionData[]} */ export function returnNodeExecutionData(responseData: ICommonObject | ICommonObject[], oAuth2RefreshedData?: any): INodeExecutionData[] { const returnData: INodeExecutionData[] = [] if (!Array.isArray(responseData)) { responseData = [responseData] } responseData.forEach((data) => { const obj = { data } as ICommonObject if (data && data.attachments) { if (Array.isArray(data.attachments) && data.attachments.length) obj.attachments = data.attachments else if (!Array.isArray(data.attachments)) obj.attachments = data.attachments } if (data && data.html) obj.html = data.html if (oAuth2RefreshedData && Object.keys(oAuth2RefreshedData).length > 0) obj[OAUTH2_REFRESHED] = oAuth2RefreshedData returnData.push(obj) }) return returnData } /** * Return responses as IWebhookNodeExecutionData * * @export * @param {(ICommonObject | ICommonObject[])} responseData * @param {string} webhookReturnResponse * @returns {IWebhookNodeExecutionData[]} */ export function returnWebhookNodeExecutionData( responseData: ICommonObject | ICommonObject[], webhookReturnResponse?: string ): IWebhookNodeExecutionData[] { const returnData: IWebhookNodeExecutionData[] = [] if (!Array.isArray(responseData)) { responseData = [responseData] } responseData.forEach((data) => { const returnObj = { data } as IWebhookNodeExecutionData if (webhookReturnResponse) returnObj.response = webhookReturnResponse returnData.push(returnObj) }) return returnData } /** * Serialize axios query params * * @export * @param {any} params * @param {boolean} skipIndex // Set to true if you want same params to be: param=1¶m=2 instead of: param[0]=1¶m[1]=2 * @returns {string} */ export function serializeQueryParams(params: any, skipIndex?: boolean): string { const parts: any[] = [] const encode = (val: string) => { return encodeURIComponent(val) .replace(/%3A/gi, ':') .replace(/%24/g, '$') .replace(/%2C/gi, ',') .replace(/%20/g, '+') .replace(/%5B/gi, '[') .replace(/%5D/gi, ']') } const convertPart = (key: string, val: any) => { if (val instanceof Date) val = val.toISOString() else if (val instanceof Object) val = JSON.stringify(val) parts.push(encode(key) + '=' + encode(val)) } Object.entries(params).forEach(([key, val]) => { if (val === null || typeof val === 'undefined') return if (Array.isArray(val)) val.forEach((v, i) => convertPart(`${key}${skipIndex ? '' : `[${i}]`}`, v)) else convertPart(key, val) }) return parts.join('&') } /** * Handle error from try catch * * @export * @param {any} error * @returns {string} */ export function handleErrorMessage(error: any): string { let errorMessage = '' if (error.message) { errorMessage += error.message + '. ' } if (error.response && error.response.data) { if (error.response.data.error) { if (typeof error.response.data.error === 'object') errorMessage += JSON.stringify(error.response.data.error) + '. ' else if (typeof error.response.data.error === 'string') errorMessage += error.response.data.error + '. ' } else if (error.response.data.msg) errorMessage += error.response.data.msg + '. ' else if (error.response.data.Message) errorMessage += error.response.data.Message + '. ' else if (typeof error.response.data === 'string') errorMessage += error.response.data + '. ' } if (!errorMessage) errorMessage = 'Unexpected Error.' return errorMessage } /** * Refresh access_token for oAuth2 apps * * @export * @param {ICommonObject} credentials */ export async function refreshOAuth2Token(credentials: ICommonObject) { const accessTokenUrl = credentials!.accessTokenUrl as string const authUrl = credentials!.authUrl as string const client_id = credentials!.clientID as string const client_secret = credentials!.clientSecret as string const refreshToken = credentials!.refresh_token as string const accessToken = credentials!.access_token as string const tokenType = credentials!.token_type as string return await refreshThroughClient(client_id, client_secret, accessTokenUrl, authUrl, accessToken, refreshToken, tokenType) } const refreshThroughClient = async ( client_id: string, client_secret: string, accessTokenUrl: string, authUrl: string, accessToken: string, refreshToken: string, tokenType: string ) => { try { const oAuth2Parameters = { clientId: client_id, clientSecret: client_secret, accessTokenUri: accessTokenUrl, authorizationUri: authUrl } const oAuthObj = new ClientOAuth2(oAuth2Parameters) const { data } = await oAuthObj.credentials.getToken() const tokenInstance = oAuthObj.createToken(accessToken, refreshToken, tokenType, data) const newToken = await tokenInstance.refresh() const { access_token, expires_in } = newToken.data const returnItem: IOAuth2RefreshResponse = { access_token, expires_in } return returnItem } catch (e) { return await refreshThroughHttpBody(client_id, client_secret, accessTokenUrl, refreshToken) } } const refreshThroughHttpBody = async (client_id: string, client_secret: string, accessTokenUrl: string, refreshToken: string) => { const method = 'POST' const axiosConfig: AxiosRequestConfig = { method, url: accessTokenUrl, data: { grant_type: 'refresh_token', client_id, client_secret, refresh_token: refreshToken }, headers: { 'Content-Type': 'application/json; charset=utf-8' } } try { const response = await axios(axiosConfig) const refreshedTokenResp = response.data const returnItem: IOAuth2RefreshResponse = { access_token: refreshedTokenResp.access_token, expires_in: refreshedTokenResp.expires_in } return returnItem } catch (e) { return await refreshThroughHttpHeader(client_id, client_secret, accessTokenUrl, refreshToken) } } const refreshThroughHttpHeader = async (client_id: string, client_secret: string, accessTokenUrl: string, refreshToken: string) => { const method = 'POST' const formData = new FormData() formData.append('grant_type', 'refresh_token') formData.append('refresh_token', refreshToken) const axiosConfig: AxiosRequestConfig = { method, url: accessTokenUrl, data: formData, headers: { ...formData.getHeaders(), Authorization: `Basic ${Buffer.from(`${client_id}:${client_secret}`).toString('base64')}` } } try { const response = await axios(axiosConfig) const refreshedTokenResp = response.data const returnItem: IOAuth2RefreshResponse = { access_token: refreshedTokenResp.access_token, expires_in: refreshedTokenResp.expires_in } return returnItem } catch (e) { throw handleErrorMessage(e) } } /** * Returns the path of node modules package * @param {string} packageName * @returns {string} */ export const getNodeModulesPackagePath = (packageName: string): string => { const checkPaths = [ path.join(__dirname, '..', 'node_modules', packageName), path.join(__dirname, '..', '..', 'node_modules', packageName), path.join(__dirname, '..', '..', '..', 'node_modules', packageName), path.join(__dirname, '..', '..', '..', '..', 'node_modules', packageName), path.join(__dirname, '..', '..', '..', '..', '..', 'node_modules', packageName) ] for (const checkPath of checkPaths) { if (fs.existsSync(checkPath)) { return checkPath } } return '' } /** * Verify valid keys * @param {string} storedKey * @param {string} suppliedKey * @returns {boolean} */ export const compareKeys = (storedKey: string, suppliedKey: string) => { const [hashedPassword, salt] = storedKey.split('.') const buffer = scryptSync(suppliedKey, salt, 64) as Buffer return timingSafeEqual(Buffer.from(hashedPassword, 'hex'), buffer) } ================================================ FILE: packages/components/tsconfig.json ================================================ { "compilerOptions": { "lib": ["es2017"], "target": "es2017" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, "experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */, "emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */, "module": "commonjs" /* Specify what module code is generated. */, "outDir": "./dist/", "resolveJsonModule": true, "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, "strict": true /* Enable all strict type-checking options. */, "skipLibCheck": true /* Skip type checking all .d.ts files. */, "sourceMap": true, "strictPropertyInitialization": false, "useUnknownInCatchVariables": false, "declaration": true }, "include": ["src", "nodes", "credentials"] } ================================================ FILE: packages/server/README.md ================================================ # Outerbridge - Automate Web3 and Web2 applications Outerbridge is a low code/no code workflow automation application, focusing on integrating both on-chain and off-chain applications. The project is licensed under [Apache License Version 2.0](https://github.com/Outerbridgeio/Outerbridge/blob/master/LICENSE.md), source available and free to self-host. ![Outerbridge](https://raw.githubusercontent.com/Outerbridgeio/Outerbridge/master/assets/outerbridge_brand.png) ## ⚡Quick Start 1. Install MongoDB [locally](https://www.mongodb.com/docs/manual/administration/install-community/) OR follow the guide of using MongoDB Atlas [here](https://docs.outerbridge.io/get-started#mongodb-atlas) 2. Install Outerbridge ```bash npm install -g outerbridge ``` 3. Start Outerbridge ```bash npx outerbridge start ``` If using MongoDB Atlas ```bash npx outerbridge start --mongourl=mongodb+srv://:@.mongodb.net/outerbridge?retryWrites=true&w=majority ``` 4. Open [http://localhost:3000](http://localhost:3000) ## 📖 Documentation Official Outerbridge docs can be found under: [https://docs.outerbridge.io](https://docs.outerbridge.io) ## 💻 Cloud Hosted - [Cloud Hosted](https://app.outerbridge.io) version of Outerbridge. ## 🌐 Self Host - Digital Ocean Droplet: [Setup guide](https://gist.github.com/HenryHengZJ/93210d43d655b4172ee50794ce473b62) - AWS EC2: [Setup guide](https://gist.github.com/HenryHengZJ/627cec19671664a88754c7e383232dc8) ## 🙋 Support Feel free to ask any questions, raise problems, and request new features in [discussion](https://github.com/Outerbridgeio/Outerbridge/discussions) ## 🙌 Contributing See [contributing guide](https://github.com/Outerbridgeio/Outerbridge/blob/master/CONTRIBUTING.md). Reach out to us at [Discord](https://discord.gg/Y9VE4ykPDJ) if you have any questions or issues. ## 📄 License Source code in this repository is made available under the [Apache License Version 2.0](https://github.com/Outerbridgeio/Outerbridge/blob/master/LICENSE.md). ================================================ FILE: packages/server/babel.config.js ================================================ module.exports = { extends: '../../babel.config.js' } ================================================ FILE: packages/server/bin/dev ================================================ #!/usr/bin/env node const oclif = require('@oclif/core') const path = require('path') const project = path.join(__dirname, '..', 'tsconfig.json') // In dev mode -> use ts-node and dev plugins process.env.NODE_ENV = 'development' require('ts-node').register({ project }) // In dev mode, always show stack traces oclif.settings.debug = true // Start the CLI oclif.run().then(oclif.flush).catch(oclif.Errors.handle) ================================================ FILE: packages/server/bin/dev.cmd ================================================ @echo off node "%~dp0\dev" %* ================================================ FILE: packages/server/bin/run ================================================ #!/usr/bin/env node const oclif = require('@oclif/core') oclif.run().then(require('@oclif/core/flush')).catch(require('@oclif/core/handle')) ================================================ FILE: packages/server/bin/run.cmd ================================================ @echo off node "%~dp0\run" %* ================================================ FILE: packages/server/nodemon.json ================================================ { "ignore": ["**/*.spec.ts", ".git", "node_modules"], "watch": ["commands", "index.ts", "src"], "exec": "yarn oclif-dev", "ext": "ts" } ================================================ FILE: packages/server/oauth2.html ================================================ Account connected! You may now close the window. ================================================ FILE: packages/server/package.json ================================================ { "name": "outerbridge", "version": "1.0.20", "description": "Outerbridge Server", "main": "dist/index", "types": "dist/index.d.ts", "bin": { "outerbridge": "./bin/run" }, "files": [ "bin", "dist", "npm-shrinkwrap.json", "oclif.manifest.json", "oauth2.html", ".env" ], "oclif": { "bin": "outerbridge", "commands": "./dist/commands" }, "scripts": { "build": "tsc", "start": "run-script-os", "start:windows": "cd bin && run start", "start:default": "cd bin && ./run start", "dev": "concurrently \"yarn watch\" \"nodemon\"", "oclif-dev": "run-script-os", "oclif-dev:windows": "cd bin && dev start", "oclif-dev:default": "cd bin && ./dev start", "postpack": "shx rm -f oclif.manifest.json", "prepack": "yarn build && oclif manifest && oclif readme", "typeorm": "typeorm-ts-node-commonjs", "watch": "tsc --watch", "version": "oclif readme && git add README.md" }, "keywords": [], "homepage": "https://outerbridge.io", "author": { "name": "Henry Heng", "email": "henryheng@outerbridge.io" }, "engines": { "node": ">=14.7.0" }, "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "@oclif/core": "^1.13.10", "axios": "^0.27.2", "client-oauth2": "^4.3.3", "cors": "^2.8.5", "crypto-js": "^4.1.1", "dotenv": "^16.0.0", "ethers": "^5.6.8", "express": "^4.17.3", "is-localhost-ip": "^2.0.0", "localtunnel": "^2.0.2", "moment-timezone": "^0.5.34", "mongodb": "^3.6.0", "outerbridge-components": "*", "outerbridge-ui": "*", "pg": "^8.4.0", "reflect-metadata": "^0.1.13", "socket.io": "^4.5.1", "typeorm": "^0.3.6" }, "devDependencies": { "@types/cors": "^2.8.12", "@types/crypto-js": "^4.1.1", "@types/localtunnel": "^2.0.1", "@types/mongodb": "^3.6.0", "concurrently": "^7.1.0", "nodemon": "^2.0.15", "oclif": "^3", "run-script-os": "^1.1.6", "shx": "^0.3.3", "ts-node": "^10.7.0", "typescript": "^4.8.4" } } ================================================ FILE: packages/server/src/ActiveTestTriggerPool.ts ================================================ import { INodeData } from 'outerbridge-components' import { IActiveTestTriggerPool, IComponentNodesPool } from './Interface' /** * This pool is to keep track of active test triggers (event listeners), * so we can clear the event listeners whenever user refresh or exit page */ export class ActiveTestTriggerPool { activeTestTriggers: IActiveTestTriggerPool = {} /** * Add to the pool * @param {string} nodeName * @param {INodeData} nodeData */ add(nodeName: string, nodeData: INodeData) { this.activeTestTriggers[nodeName] = nodeData } /** * Remove all triggers from the pool * @param {IComponentNodesPool} componentNodes */ async removeAll(componentNodes: IComponentNodesPool) { const toBeDeleted: string[] = [] for (const nodeName in this.activeTestTriggers) { const triggerNodeInstance = componentNodes[nodeName] await triggerNodeInstance.removeTrigger!.call(triggerNodeInstance, this.activeTestTriggers[nodeName]) toBeDeleted.push(nodeName) } for (const nodeName in toBeDeleted) { delete this.activeTestTriggers[nodeName] } } /** * Remove single trigger from the pool * @param {string} nodeName * @param {IComponentNodesPool} componentNodes */ async remove(nodeName: string, componentNodes: IComponentNodesPool) { if (Object.prototype.hasOwnProperty.call(this.activeTestTriggers, nodeName)) { const triggerNodeInstance = componentNodes[nodeName] await triggerNodeInstance.removeTrigger!.call(triggerNodeInstance, this.activeTestTriggers[nodeName]) delete this.activeTestTriggers[nodeName] } } } ================================================ FILE: packages/server/src/ActiveTestWebhookPool.ts ================================================ import { INodeData } from 'outerbridge-components' import { IActiveTestWebhookPool, IComponentNodesPool, IReactFlowEdge, IReactFlowNode, WebhookMethod } from './Interface' /** * This pool is to keep track of active test webhooks, * so we can clear the webhooks whenever user refresh or exit page */ export class ActiveTestWebhookPool { activeTestWebhooks: IActiveTestWebhookPool = {} /** * Add to the pool * @param {string} webhookEndpoint * @param {WebhookMethod} httpMethod * @param {IReactFlowNode[]} nodes * @param {IReactFlowEdge[]} edges * @param {INodeData} nodeData * @param {string} clientId * @param {boolean} isTestWorkflow * @param {string} webhookId */ add( webhookEndpoint: string, httpMethod: WebhookMethod, nodes: IReactFlowNode[], edges: IReactFlowEdge[], nodeData: INodeData, webhookNodeId: string, clientId: string, isTestWorkflow: boolean, webhookId?: string ) { const key = `${webhookEndpoint}_${httpMethod}` this.activeTestWebhooks[key] = { nodes, edges, nodeData, clientId, webhookNodeId, isTestWorkflow, webhookId } } /** * Remove all webhooks from the pool * @param {IComponentNodesPool} componentNodes */ async removeAll(componentNodes: IComponentNodesPool) { const toBeDeleted: string[] = [] for (const key in this.activeTestWebhooks) { const { nodeData, webhookId } = this.activeTestWebhooks[key] const nodeName = nodeData.name const webhookNodeInstance = componentNodes[nodeName] // Delete webhook from 3rd party apps if (webhookId) { await webhookNodeInstance.webhookMethods?.deleteWebhook(nodeData, webhookId) } toBeDeleted.push(key) } for (const key in toBeDeleted) { delete this.activeTestWebhooks[key] } } /** * Remove single webhook from the pool * @param {string} testWebhookKey * @param {IComponentNodesPool} componentNodes */ async remove(testWebhookKey: string, componentNodes: IComponentNodesPool) { if (Object.prototype.hasOwnProperty.call(this.activeTestWebhooks, testWebhookKey)) { const { nodeData, webhookId } = this.activeTestWebhooks[testWebhookKey] const nodeName = nodeData.name const webhookNodeInstance = componentNodes[nodeName] // Delete webhook from 3rd party apps if (webhookId) { await webhookNodeInstance.webhookMethods?.deleteWebhook(nodeData, webhookId) } delete this.activeTestWebhooks[testWebhookKey] } } } ================================================ FILE: packages/server/src/ChildProcess.ts ================================================ import { ICommonObject, INodeData, INodeExecutionData } from 'outerbridge-components' import { IChildProcessMessage, IExploredNode, INodeQueue, IRunWorkflowMessageValue, IVariableDict, IWorkflowExecutedData } from './Interface' import { checkOAuth2TokenRefreshed, decryptCredentials } from './utils' import { DataSource } from 'typeorm' import { Workflow } from './entity/Workflow' import { Execution } from './entity/Execution' import { Credential } from './entity/Credential' import { Webhook } from './entity/Webhook' import { Contract } from './entity/Contract' import { Wallet } from './entity/Wallet' import lodash from 'lodash' export class ChildProcess { /** * Stop child process after 5 secs timeout */ static async stopChildProcess() { setTimeout(() => { process.exit(0) }, 50000) } /** * Run the workflow using Breadth First Search Topological Sort * @param {IRunWorkflowMessageValue} messageValue * @return {Promise} */ async runWorkflow(messageValue: IRunWorkflowMessageValue): Promise { process.on('SIGTERM', ChildProcess.stopChildProcess) process.on('SIGINT', ChildProcess.stopChildProcess) await sendToParentProcess('start', '_') const childAppDataSource = await initDB() // Create a Queue and add our initial node in it const { startingNodeIds, componentNodes, reactFlowNodes, reactFlowEdges, graph, workflowExecutedData } = messageValue const nodeQueue = [] as INodeQueue[] const exploredNode = {} as IExploredNode // In the case of infinite loop, only max 3 loops will be executed const maxLoop = 3 for (let i = 0; i < startingNodeIds.length; i += 1) { nodeQueue.push({ nodeId: startingNodeIds[i], depth: 0 }) exploredNode[startingNodeIds[i]] = { remainingLoop: maxLoop, lastSeenDepth: 0 } } while (nodeQueue.length) { const { nodeId, depth } = nodeQueue.shift() as INodeQueue const ignoreNodeIds: string[] = [] if (!startingNodeIds.includes(nodeId)) { const reactFlowNode = reactFlowNodes.find((nd) => nd.id === nodeId) if (!reactFlowNode || reactFlowNode === undefined) continue try { const nodeInstanceFilePath = componentNodes[reactFlowNode.data.name].filePath const nodeModule = await import(nodeInstanceFilePath) const newNodeInstance = new nodeModule.nodeClass() await decryptCredentials(reactFlowNode.data, childAppDataSource) const reactFlowNodeData: INodeData[] = resolveVariables(reactFlowNode.data, workflowExecutedData) let results: INodeExecutionData[] = [] for (let i = 0; i < reactFlowNodeData.length; i += 1) { const result = await newNodeInstance.run!.call(newNodeInstance, reactFlowNodeData[i]) checkOAuth2TokenRefreshed(result, reactFlowNodeData[i], childAppDataSource) if (result) results.push(...result) } // Determine which nodes to route next when it comes to ifElse if (results.length && nodeId.includes('ifElse')) { let anchorIndex = -1 if (Array.isArray(results) && Object.keys((results as any)[0].data).length === 0) { anchorIndex = 0 } else if (Array.isArray(results) && Object.keys((results as any)[1].data).length === 0) { anchorIndex = 1 } const ifElseEdge = reactFlowEdges.find( (edg) => edg.source === nodeId && edg.sourceHandle === `${nodeId}-output-${anchorIndex}` ) if (ifElseEdge) { ignoreNodeIds.push(ifElseEdge.target) } } const newWorkflowExecutedData = { nodeId, nodeLabel: reactFlowNode.data.label, data: results } as IWorkflowExecutedData workflowExecutedData.push(newWorkflowExecutedData) } catch (e: any) { // console.error(e); console.error(e.message) const newWorkflowExecutedData = { nodeId, nodeLabel: reactFlowNode.data.label, data: [{ error: e.message }] } as IWorkflowExecutedData workflowExecutedData.push(newWorkflowExecutedData) await sendToParentProcess('error', workflowExecutedData) return } } const neighbourNodeIds = graph[nodeId] const nextDepth = depth + 1 for (let i = 0; i < neighbourNodeIds.length; i += 1) { const neighNodeId = neighbourNodeIds[i] if (!ignoreNodeIds.includes(neighNodeId)) { // If nodeId has been seen, cycle detected if (Object.prototype.hasOwnProperty.call(exploredNode, neighNodeId)) { const { remainingLoop, lastSeenDepth } = exploredNode[neighNodeId] if (lastSeenDepth === nextDepth) continue if (remainingLoop === 0) { break } const remainingLoopMinusOne = remainingLoop - 1 exploredNode[neighNodeId] = { remainingLoop: remainingLoopMinusOne, lastSeenDepth: nextDepth } nodeQueue.push({ nodeId: neighNodeId, depth: nextDepth }) } else { exploredNode[neighNodeId] = { remainingLoop: maxLoop, lastSeenDepth: nextDepth } nodeQueue.push({ nodeId: neighNodeId, depth: nextDepth }) } } } } await sendToParentProcess('finish', workflowExecutedData) } } /** * Initalize DB in child process * @returns {DataSource} */ async function initDB() { const childAppDataSource = new DataSource({ type: 'mongodb', url: process.env.MONGO_URL || `mongodb://${process.env.MONGO_HOST || '0.0.0.0'}:27017/outerbridge`, useNewUrlParser: true, synchronize: true, logging: false, entities: [Workflow, Execution, Credential, Webhook, Contract, Wallet], migrations: [], subscribers: [] }) return await childAppDataSource.initialize() } /** * Get variable value from outputResponses.output * @param {string} paramValue * @param {IWorkflowExecutedData[]} workflowExecutedData * @param {string} key * @param {number} loopIndex * @returns {string} */ function getVariableValue(paramValue: string, workflowExecutedData: IWorkflowExecutedData[], key: string, loopIndex: number): string { let returnVal = paramValue const variableStack = [] const variableDict = {} as IVariableDict let startIdx = 0 const endIdx = returnVal.length - 1 while (startIdx < endIdx) { const substr = returnVal.substring(startIdx, startIdx + 2) // Store the opening double curly bracket if (substr === '{{') { variableStack.push({ substr, startIdx: startIdx + 2 }) } // Found the complete variable if (substr === '}}' && variableStack.length > 0 && variableStack[variableStack.length - 1].substr === '{{') { const variableStartIdx = variableStack[variableStack.length - 1].startIdx const variableEndIdx = startIdx const variableFullPath = returnVal.substring(variableStartIdx, variableEndIdx) // Split by first occurence of '[' to get just nodeId const [variableNodeId, ...rest] = variableFullPath.split('[') let variablePath = 'data' + '[' + rest.join('[') if (variablePath.includes('$index')) { variablePath = variablePath.split('$index').join(loopIndex.toString()) } const executedNodeData = workflowExecutedData.find((exec) => exec.nodeId === variableNodeId) if (executedNodeData) { const resolvedVariablePath = getVariableValue(variablePath, workflowExecutedData, key, loopIndex) const variableValue = lodash.get(executedNodeData, resolvedVariablePath) variableDict[`{{${variableFullPath}}}`] = variableValue // For instance: const var1 = "some var" if (key === 'code' && typeof variableValue === 'string') variableDict[`{{${variableFullPath}}}`] = `"${variableValue}"` if (key === 'code' && typeof variableValue === 'object') variableDict[`{{${variableFullPath}}}`] = `${JSON.stringify(variableValue)}` } variableStack.pop() } startIdx += 1 } const variablePaths = Object.keys(variableDict) variablePaths.sort() // Sort by length of variable path because longer path could possibly contains nested variable variablePaths.forEach((path) => { const variableValue = variableDict[path] // Replace all occurence returnVal = returnVal.split(path).join(variableValue) }) return returnVal } /** * Get minimum variable array length from outputResponses.output * @param {string} paramValue * @param {IReactFlowNode[]} reactFlowNodes * @returns {number} */ export const getVariableLength = (paramValue: string, workflowExecutedData: IWorkflowExecutedData[]): number => { let minLoop = Infinity const variableStack = [] let startIdx = 0 const endIdx = paramValue.length - 1 while (startIdx < endIdx) { const substr = paramValue.substring(startIdx, startIdx + 2) // Store the opening double curly bracket if (substr === '{{') { variableStack.push({ substr, startIdx: startIdx + 2 }) } // Found the complete variable if (substr === '}}' && variableStack.length > 0 && variableStack[variableStack.length - 1].substr === '{{') { const variableStartIdx = variableStack[variableStack.length - 1].startIdx const variableEndIdx = startIdx const variableFullPath = paramValue.substring(variableStartIdx, variableEndIdx) if (variableFullPath.includes('$index')) { // Split by first occurence of '[' to get just nodeId const [variableNodeId, ...rest] = variableFullPath.split('[') const variablePath = 'data' + '[' + rest.join('[') const [variableArrayPath, ..._] = variablePath.split('[$index]') const executedNodeData = workflowExecutedData.find((exec) => exec.nodeId === variableNodeId) if (executedNodeData) { const variableValue = lodash.get(executedNodeData, variableArrayPath) if (Array.isArray(variableValue)) minLoop = Math.min(minLoop, variableValue.length) } } variableStack.pop() } startIdx += 1 } return minLoop } /** * Loop through each inputs and resolve variable if neccessary * @param {INodeData} reactFlowNodeData * @param {IWorkflowExecutedData[]} workflowExecutedData * @returns {INodeData} */ function resolveVariables(reactFlowNodeData: INodeData, workflowExecutedData: IWorkflowExecutedData[]): INodeData[] { const flowNodeDataArray: INodeData[] = [] const flowNodeData = lodash.cloneDeep(reactFlowNodeData) const types = ['actions', 'networks', 'inputParameters'] const getMinForLoop = (paramsObj: ICommonObject) => { let minLoop = Infinity for (const key in paramsObj) { const paramValue = paramsObj[key] if (typeof paramValue === 'string' && paramValue.includes('$index')) { // node.data[$index].smtg minLoop = Math.min(minLoop, getVariableLength(paramValue, workflowExecutedData)) } if (Array.isArray(paramValue)) { for (let j = 0; j < paramValue.length; j += 1) { minLoop = Math.min(minLoop, getMinForLoop(paramValue[j] as ICommonObject)) } } } return minLoop } const getParamValues = (paramsObj: ICommonObject, loopIndex: number) => { for (const key in paramsObj) { const paramValue = paramsObj[key] if (typeof paramValue === 'string') { const resolvedValue = getVariableValue(paramValue, workflowExecutedData, key, loopIndex) paramsObj[key] = resolvedValue } if (typeof paramValue === 'number') { const paramValueStr = paramValue.toString() const resolvedValue = getVariableValue(paramValueStr, workflowExecutedData, key, loopIndex) paramsObj[key] = resolvedValue } if (Array.isArray(paramValue)) { for (let j = 0; j < paramValue.length; j += 1) { getParamValues(paramValue[j] as ICommonObject, loopIndex) } } } } let minLoop = Infinity for (let i = 0; i < types.length; i += 1) { const paramsObj = (flowNodeData as any)[types[i]] minLoop = Math.min(minLoop, getMinForLoop(paramsObj)) } if (minLoop === Infinity) { for (let i = 0; i < types.length; i += 1) { const paramsObj = (flowNodeData as any)[types[i]] getParamValues(paramsObj, -1) } return [flowNodeData] } else { for (let j = 0; j < minLoop; j += 1) { const clonedFlowNodeData = lodash.cloneDeep(flowNodeData) for (let i = 0; i < types.length; i += 1) { const paramsObj = (clonedFlowNodeData as any)[types[i]] getParamValues(paramsObj, j) } flowNodeDataArray.push(clonedFlowNodeData) } return flowNodeDataArray } } /** * Send data back to parent process * @param {string} key Key of message * @param {*} value Value of message * @returns {Promise} */ async function sendToParentProcess(key: string, value: any): Promise { // tslint:disable-line:no-any return new Promise((resolve, reject) => { process.send!( { key, value }, (error: Error) => { if (error) { return reject(error) } resolve() } ) }) } const childProcess = new ChildProcess() process.on('message', async (message: IChildProcessMessage) => { if (message.key === 'start') { await childProcess.runWorkflow(message.value) process.exit() } }) ================================================ FILE: packages/server/src/CredentialsPool.ts ================================================ import { INodeCredential } from 'outerbridge-components' import { IComponentCredentialsPool } from './Interface' import path from 'path' import { Dirent } from 'fs' import { getNodeModulesPackagePath } from './utils' import { promises } from 'fs' export class CredentialsPool { componentCredentials: IComponentCredentialsPool = {} /** * Initialize to get all credentials */ async initialize() { const packagePath = getNodeModulesPackagePath('outerbridge-components') const credPath = path.join(packagePath, 'dist', 'credentials') const credFiles = await this.getFiles(credPath) return Promise.all( credFiles.map(async (file) => { if (file.endsWith('.js')) { const credModule = await import(file) const newCredInstance: INodeCredential = new credModule.credClass() this.componentCredentials[newCredInstance.name] = newCredInstance } }) ) } /** * Recursive function to get credential files * @param {string} dir * @returns {string[]} */ async getFiles(dir: string): Promise { const dirents = await promises.readdir(dir, { withFileTypes: true }) const files = await Promise.all( dirents.map((dirent: Dirent) => { const res = path.resolve(dir, dirent.name) return dirent.isDirectory() ? this.getFiles(res) : res }) ) return Array.prototype.concat(...files) } } ================================================ FILE: packages/server/src/DataSource.ts ================================================ import 'reflect-metadata' import { DataSource } from 'typeorm' import { Workflow } from './entity/Workflow' import { Execution } from './entity/Execution' import { Credential } from './entity/Credential' import { Webhook } from './entity/Webhook' import { Contract } from './entity/Contract' import { Wallet } from './entity/Wallet' let appDataSource: DataSource export async function init(): Promise { appDataSource = new DataSource({ type: 'mongodb', url: process.env.MONGO_URL || `mongodb://${process.env.MONGO_HOST || '0.0.0.0'}:27017/outerbridge`, useNewUrlParser: true, useUnifiedTopology: true, synchronize: true, logging: false, entities: [Workflow, Execution, Credential, Webhook, Contract, Wallet], migrations: [], subscribers: [] }) } export function getDataSource(): DataSource { if (appDataSource === undefined) { init() } return appDataSource } ================================================ FILE: packages/server/src/DeployedWorkflowPool.ts ================================================ import { INodeData, INodeExecutionData, IWebhookNodeExecutionData } from 'outerbridge-components' import { IDeployedWorkflowsPool, IComponentNodesPool, INodeDirectedGraph, ITriggerNode, IChildProcessMessage, IWorkflowExecutedData, ExecutionState, IReactFlowNode, IReactFlowObject, IReactFlowEdge, IRunWorkflowMessageValue, WebhookMethod, IWebhookNode } from './Interface' import { DataSource } from 'typeorm' import { join as pathJoin } from 'path' import { fork } from 'child_process' import * as fs from 'fs' import lodash from 'lodash' import { ObjectId } from 'mongodb' import { constructGraphs, getStartingNode, decryptCredentials } from './utils' import { Webhook } from './entity/Webhook' import { Execution } from './entity/Execution' import { Workflow } from './entity/Workflow' import { ActiveTestTriggerPool } from './ActiveTestTriggerPool' import { ActiveTestWebhookPool } from './ActiveTestWebhookPool' export class DeployedWorkflowPool { deployedWorkflows: IDeployedWorkflowsPool = {} AppDataSource: DataSource /** * Initialize to get all deployed workflows * @param {DataSource} AppDataSource */ async initialize(AppDataSource: DataSource, componentNodes: IComponentNodesPool) { this.deployedWorkflows = {} this.AppDataSource = AppDataSource const workflows = await this.AppDataSource.getMongoRepository(Workflow).find() for (let i = 0; i < workflows.length; i += 1) { const workflow = workflows[i] if (workflow.deployed) { try { const flowDataString = workflow.flowData const flowData: IReactFlowObject = JSON.parse(flowDataString) const reactFlowNodes = flowData.nodes as IReactFlowNode[] const reactFlowEdges = flowData.edges as IReactFlowEdge[] const workflowShortId = workflow.shortId const { graph, nodeDependencies } = constructGraphs(reactFlowNodes, reactFlowEdges) const { faultyNodeLabels, startingNodeIds } = getStartingNode(nodeDependencies, reactFlowNodes) // If there are faulty nodes and deployed is active, update deployed to false if (faultyNodeLabels.length) { const body = { deployed: false } const updateWorkflow = new Workflow() Object.assign(updateWorkflow, body) AppDataSource.getMongoRepository(Workflow).merge(workflow, updateWorkflow) await AppDataSource.getMongoRepository(Workflow).save(workflow) continue } await this.add(startingNodeIds, graph, reactFlowNodes, componentNodes, workflowShortId) } catch (e) { throw new Error(`Error initializing workflow ${workflow.shortId}: ${e}`) } } } } /** * Add workflow to deployed workflow pools * @param {string[]} startingNodeIds * @param {INodeDirectedGraph} graph * @param {IReactFlowNode[]} reactFlowNodes * @param {IComponentNodesPool} componentNodes * @param {string} workflowShortId */ async add( startingNodeIds: string[], graph: INodeDirectedGraph, reactFlowNodes: IReactFlowNode[], componentNodes: IComponentNodesPool, workflowShortId: string, activeTestTriggerPool?: ActiveTestTriggerPool, activeTestWebhookPool?: ActiveTestWebhookPool ) { for (let i = 0; i < startingNodeIds.length; i += 1) { const startingNodeId = startingNodeIds[i] const startNode = reactFlowNodes.find((nd) => nd.id === startingNodeId) const emitEventKey = `${workflowShortId}_${startingNodeId}` if (startNode && startNode.data && startNode.data.type === 'trigger') { const nodeInstance = componentNodes[startNode.data.name] const triggerNodeInstance = nodeInstance as ITriggerNode // Remove test trigger if (activeTestTriggerPool) await activeTestTriggerPool.remove(startNode.data.name, componentNodes) triggerNodeInstance.on(emitEventKey, async (result: INodeExecutionData[]) => { await this.startWorkflow(workflowShortId, startNode, startingNodeId, result, componentNodes, startingNodeIds, graph) }) await decryptCredentials(startNode.data) const nodeData = { ...startNode.data, emitEventKey, nodeId: startingNodeId, workflowShortId } as INodeData await triggerNodeInstance.runTrigger!.call(triggerNodeInstance, nodeData) } else if (startNode && startNode.data && startNode.data.type === 'webhook') { const nodeInstance = componentNodes[startNode.data.name] const webhookNodeInstance = nodeInstance as IWebhookNode const newBody = { nodeId: startingNodeId, webhookEndpoint: startNode.data.webhookEndpoint, httpMethod: (startNode.data.inputParameters?.httpMethod as WebhookMethod) || 'POST', workflowShortId } as any // Remove test webhook if (activeTestWebhookPool) await activeTestWebhookPool.remove(`${newBody.webhookEndpoint}_${newBody.httpMethod}`, componentNodes) const foundWebhook = await this.AppDataSource.getMongoRepository(Webhook).findOneBy(newBody) // If third party webhook exists, delete and re-create with new tunnel if (foundWebhook && foundWebhook.webhookId && process.env.ENABLE_TUNNEL === 'true') { if (!process.env.TUNNEL_BASE_URL) { return } await this.AppDataSource.getMongoRepository(Webhook).deleteOne({ _id: new ObjectId(foundWebhook._id) }) await decryptCredentials(startNode.data) await webhookNodeInstance.webhookMethods?.deleteWebhook(startNode.data, foundWebhook.webhookId) const webhookFullUrl = `${process.env.TUNNEL_BASE_URL}api/v1/webhook/${startNode.data.webhookEndpoint}` const webhookId = await webhookNodeInstance.webhookMethods?.createWebhook.call( webhookNodeInstance, startNode.data, webhookFullUrl ) if (webhookId !== undefined) { newBody.webhookId = webhookId } const newWebhook = new Webhook() Object.assign(newWebhook, newBody) const webhook = await this.AppDataSource.getMongoRepository(Webhook).create(newWebhook) await this.AppDataSource.getMongoRepository(Webhook).save(webhook) } if (!foundWebhook) { if (webhookNodeInstance.webhookMethods?.createWebhook) { if (!process.env.TUNNEL_BASE_URL) { return } const webhookFullUrl = `${process.env.TUNNEL_BASE_URL}api/v1/webhook/${startNode.data.webhookEndpoint}` await decryptCredentials(startNode.data) const webhookId = await webhookNodeInstance.webhookMethods?.createWebhook.call( webhookNodeInstance, startNode.data, webhookFullUrl ) if (webhookId !== undefined) { newBody.webhookId = webhookId } } const newWebhook = new Webhook() Object.assign(newWebhook, newBody) const webhook = await this.AppDataSource.getMongoRepository(Webhook).create(newWebhook) await this.AppDataSource.getMongoRepository(Webhook).save(webhook) } } if (Object.prototype.hasOwnProperty.call(this.deployedWorkflows, workflowShortId)) { this.deployedWorkflows[workflowShortId].emitEventKey = emitEventKey } else { this.deployedWorkflows[workflowShortId] = { emitEventKey } } } } /** * Start the rest of workflow via child process * @param {string} workflowShortId * @param {IReactFlowNode} startNode * @param {string} startingNodeId * @param {INodeExecutionData[] | IWebhookNodeExecutionData[]} startingNodeExecutedData * @param {IComponentNodesPool} componentNodes * @param {string[]} startingNodeIds * @param {INodeDirectedGraph} graph */ async startWorkflow( workflowShortId: string, startNode: IReactFlowNode, startingNodeId: string, startingNodeExecutedData: INodeExecutionData[] | IWebhookNodeExecutionData[], componentNodes: IComponentNodesPool, startingNodeIds: string[], graph: INodeDirectedGraph ) { try { // Fetch latest nodes and edges from DB const { reactFlowNodes, reactFlowEdges } = await this.prepareDataForChildProcess(workflowShortId) const controller = new AbortController() const { signal } = controller let childpath = pathJoin(__dirname, '..', 'dist', 'ChildProcess.js') if (!fs.existsSync(childpath)) childpath = 'ChildProcess.ts' const childProcess = fork(childpath, [], { signal }) const workflowExecutedData = [ { nodeId: startingNodeId, nodeLabel: startNode.data.label, data: startingNodeExecutedData } ] as IWorkflowExecutedData[] const newExecution = await this.addExecution(workflowShortId, workflowExecutedData, controller) const newExecutionShortId = newExecution === undefined ? '' : newExecution.shortId // Remove cronJobs and providers to avoid error of converting circular structure to JSON const clonedComponentNodes = lodash.cloneDeep(componentNodes) for (const nodeInstanceName in clonedComponentNodes) { if (Object.prototype.hasOwnProperty.call(clonedComponentNodes[nodeInstanceName], 'providers')) { delete (clonedComponentNodes[nodeInstanceName] as any)['providers'] } if (Object.prototype.hasOwnProperty.call(clonedComponentNodes[nodeInstanceName], 'cronJobs')) { delete (clonedComponentNodes[nodeInstanceName] as any)['cronJobs'] } } const value = { startingNodeIds, componentNodes: clonedComponentNodes, reactFlowNodes, reactFlowEdges, graph, workflowExecutedData } as IRunWorkflowMessageValue childProcess.send({ key: 'start', value } as IChildProcessMessage) let childProcessTimeout: NodeJS.Timeout return new Promise((resolve, _) => { childProcess.on('message', async (message: IChildProcessMessage) => { if (message.key === 'finish') { let updatedWorkflowExecutedData = message.value as IWorkflowExecutedData[] updatedWorkflowExecutedData = updatedWorkflowExecutedData.filter((execData) => execData.nodeId !== startingNodeId) await this.updateExecution(workflowShortId, updatedWorkflowExecutedData, newExecutionShortId, 'FINISHED') if (childProcessTimeout) { clearTimeout(childProcessTimeout) } resolve(updatedWorkflowExecutedData) } if (message.key === 'start') { if (process.env.EXECUTION_TIMEOUT) { childProcessTimeout = setTimeout(async () => { childProcess.kill() await this.terminateSpecificExecutionAfterTimeout(newExecutionShortId) }, parseInt(process.env.EXECUTION_TIMEOUT, 10)) } } if (message.key === 'error') { let updatedWorkflowExecutedData = message.value as IWorkflowExecutedData[] updatedWorkflowExecutedData = updatedWorkflowExecutedData.filter((execData) => execData.nodeId !== startingNodeId) await this.updateExecution(workflowShortId, updatedWorkflowExecutedData, newExecutionShortId, 'ERROR') if (childProcessTimeout) { clearTimeout(childProcessTimeout) } resolve(updatedWorkflowExecutedData) } }) }) } catch (err) { console.error(err) } } /** * Remove workflow from deployed workflow pools by: * 1.) Remove trigger 2.) Trigger abort controller 3.) Delete the object from deployedWorkflowsPool * @param {string[]} startingNodeIds * @param {IReactFlowNode[]} reactFlowNodes * @param {IComponentNodesPool} componentNodes * @param {string} workflowShortId */ async remove( startingNodeIds: string[], reactFlowNodes: IReactFlowNode[], componentNodes: IComponentNodesPool, workflowShortId: string ) { for (let i = 0; i < startingNodeIds.length; i += 1) { const startingNodeId = startingNodeIds[i] const startNode = reactFlowNodes.find((nd) => nd.id === startingNodeId) const emitEventKey = `${workflowShortId}_${startingNodeId}` if (startNode && startNode.data && startNode.data.type === 'trigger') { const nodeInstance = componentNodes[startNode.data.name] const triggerNodeInstance = nodeInstance as ITriggerNode const nodeData = { ...startNode.data, emitEventKey, nodeId: startingNodeId, workflowShortId } as INodeData await triggerNodeInstance.removeTrigger!.call(triggerNodeInstance, nodeData) } else if (startNode && startNode.data && startNode.data.type === 'webhook') { const nodeInstance = componentNodes[startNode.data.name] const webhookNodeInstance = nodeInstance as IWebhookNode const query = { nodeId: startingNodeId, webhookEndpoint: startNode.data.webhookEndpoint, httpMethod: (startNode.data.inputParameters?.httpMethod as WebhookMethod) || 'POST', workflowShortId } as any const webhook = await this.AppDataSource.getMongoRepository(Webhook).findOneBy(query) if (webhook && webhook.webhookId) { await decryptCredentials(startNode.data) const isWebhookDeleted = await webhookNodeInstance.webhookMethods?.deleteWebhook(startNode.data, webhook.webhookId) if (isWebhookDeleted) { query.webhookId = webhook.webhookId } } await this.AppDataSource.getMongoRepository(Webhook).delete(query) } await this.terminateExecutions(workflowShortId) if ( this.deployedWorkflows[workflowShortId] && this.deployedWorkflows[workflowShortId].abortController !== undefined && !this.deployedWorkflows[workflowShortId].abortController?.signal.aborted ) { const workflowAbortController = this.deployedWorkflows[workflowShortId].abortController delete this.deployedWorkflows[workflowShortId] try { workflowAbortController?.abort() } catch (e) { // console.error(e); } } } } /** * Remove all deployed workflows from pools: * @param {IComponentNodesPool} componentNodes */ async removeAll(componentNodes: IComponentNodesPool) { const deployedWorkflowShortIds = Object.keys(this.deployedWorkflows) if (!deployedWorkflowShortIds.length) return const workflows = await this.AppDataSource.getMongoRepository(Workflow).findBy({ shortId: { $in: deployedWorkflowShortIds } }) for (let i = 0; i < workflows.length; i += 1) { const workflow = workflows[i] const flowDataString = workflow.flowData const flowData: IReactFlowObject = JSON.parse(flowDataString) const reactFlowNodes = flowData.nodes const reactFlowEdges = flowData.edges const { nodeDependencies } = constructGraphs(reactFlowNodes, reactFlowEdges) const { startingNodeIds } = getStartingNode(nodeDependencies, reactFlowNodes) await this.remove(startingNodeIds, reactFlowNodes, componentNodes, workflow.shortId) } } /** * Get nodes and edges from database * @param {string} workflowShortId */ async prepareDataForChildProcess(workflowShortId: string) { const workflow = await this.AppDataSource.getMongoRepository(Workflow).findOneBy({ shortId: workflowShortId }) if (!workflow) { throw new Error(`Workflow ${workflowShortId} not found`) } const flowDataString = workflow.flowData const flowData: IReactFlowObject = JSON.parse(flowDataString) const reactFlowNodes = flowData.nodes const reactFlowEdges = flowData.edges return { reactFlowNodes, reactFlowEdges } } /** * Add results to deployedWorkflows[workflowShortId].workflowExecutedData, and Execution DB * @param {string} workflowShortId * @param {IWorkflowExecutedData[]} workflowExecutedData * @param {AbortController} controller */ async addExecution(workflowShortId: string, workflowExecutedData: IWorkflowExecutedData[], controller: AbortController) { if (!Object.prototype.hasOwnProperty.call(this.deployedWorkflows, workflowShortId)) { return } this.deployedWorkflows[workflowShortId].workflowExecutedData = workflowExecutedData this.deployedWorkflows[workflowShortId].abortController = controller const newExecution = new Execution() const bodyExecution = { workflowShortId, state: 'INPROGRESS' as ExecutionState, executionData: JSON.stringify(this.deployedWorkflows[workflowShortId].workflowExecutedData) } Object.assign(newExecution, bodyExecution) const execution = this.AppDataSource.getMongoRepository(Execution).create(newExecution) return await this.AppDataSource.getMongoRepository(Execution).save(execution) } /** * Update results to deployedWorkflows[workflowShortId].workflowExecutedData, and Execution DB * @param {string} workflowShortId * @param {IWorkflowExecutedData[]} workflowExecutedData * @param {string} executionShortId * @param {ExecutionState} state */ async updateExecution( workflowShortId: string, workflowExecutedData: IWorkflowExecutedData[], executionShortId: string, state: ExecutionState ) { if (!Object.prototype.hasOwnProperty.call(this.deployedWorkflows, workflowShortId)) { return } const existingWorkflowExecutedData = this.deployedWorkflows[workflowShortId].workflowExecutedData || [] const updateWorkflowExecutedData = existingWorkflowExecutedData.concat(workflowExecutedData) this.deployedWorkflows[workflowShortId].workflowExecutedData = updateWorkflowExecutedData const execution = await this.AppDataSource.getMongoRepository(Execution).findOneBy({ shortId: executionShortId }) if (!execution) { throw new Error(`Execution ${executionShortId} not found`) } const updateExecution = new Execution() const bodyExecution = { state, executionData: JSON.stringify(this.deployedWorkflows[workflowShortId].workflowExecutedData), stoppedDate: new Date() } Object.assign(updateExecution, bodyExecution) this.AppDataSource.getMongoRepository(Execution).merge(execution, updateExecution) await this.AppDataSource.getMongoRepository(Execution).save(execution) } /** * Update INPROGRESS execution to TERMINATED * @param {string} workflowShortId */ async terminateExecutions(workflowShortId: string) { const executions: Execution[] = await this.AppDataSource.getMongoRepository(Execution) .aggregate([ { $match: { workflowShortId, state: 'INPROGRESS' as ExecutionState } } ]) .toArray() for (let i = 0; i < executions.length; i += 1) { const execution = executions[i] const body = { state: 'TERMINATED' as ExecutionState, stoppedDate: new Date() } const updateExecution = new Execution() Object.assign(updateExecution, body) this.AppDataSource.getMongoRepository(Execution).merge(execution, updateExecution) await this.AppDataSource.getMongoRepository(Execution).save(execution) } } /** * Update specific execution to TIMEOUT * @param {string} newExecutionShortId */ async terminateSpecificExecutionAfterTimeout(newExecutionShortId: string) { const execution = await this.AppDataSource.getMongoRepository(Execution).findOneBy({ shortId: newExecutionShortId }) if (execution) { const body = { state: 'TIMEOUT' as ExecutionState, stoppedDate: new Date() } const updateExecution = new Execution() Object.assign(updateExecution, body) this.AppDataSource.getMongoRepository(Execution).merge(execution, updateExecution) await this.AppDataSource.getMongoRepository(Execution).save(execution) } } } ================================================ FILE: packages/server/src/Interface.ts ================================================ import { ObjectId } from 'mongodb' import { ICommonObject, INode as INodeComponent, INodeCredential, INodeData, INodeExecutionData, IWebhookNodeExecutionData } from 'outerbridge-components' import EventEmitter from 'events' /** * Databases */ export interface IWorkflow { _id: ObjectId shortId: string name: string flowData: string deployed: boolean updatedDate: Date createdDate: Date } export interface IExecution { _id: ObjectId shortId: string workflowShortId: string executionData: string state: ExecutionState createdDate: Date stoppedDate?: Date } export interface ICredential { _id: ObjectId name: string nodeCredentialName: string credentialData: string updatedDate: Date createdDate: Date } export interface IWebhook { _id: ObjectId workflowShortId: string webhookEndpoint: string httpMethod: WebhookMethod webhookId: string nodeId: string updatedDate: Date createdDate: Date } export interface IContract { _id: ObjectId name: string abi: string address: string network: string providerCredential: string updatedDate: Date createdDate: Date } export interface IWallet { _id: ObjectId name: string address: string network: string providerCredential: string walletCredential: string updatedDate: Date createdDate: Date } /** * Types */ export type ExecutionState = 'INPROGRESS' | 'FINISHED' | 'ERROR' | 'TERMINATED' | 'TIMEOUT' export type WebhookMethod = 'GET' | 'POST' /** * Others */ export interface IWorkflowResponse extends IWorkflow { execution: IExecution executionCount: number } export interface INode extends INodeComponent { filePath: string } export interface ITriggerNode extends EventEmitter, INodeComponent { filePath: string } export interface IWebhookNode extends INodeComponent { filePath: string } export interface IComponentNodesPool { [key: string]: INode | ITriggerNode } export interface IActiveTestTriggerPool { [key: string]: INodeData } export interface IActiveTestWebhookPool { [key: string]: { nodes: IReactFlowNode[] edges: IReactFlowEdge[] nodeData: INodeData webhookNodeId: string clientId: string isTestWorkflow: boolean webhookId?: string } } export interface ICredentialBody { name: string nodeCredentialName: string credentialData: ICredentialDataDecrypted } export interface ICredentialResponse { _id: ObjectId name: string credentialData: ICredentialDataDecrypted nodeCredentialName: string updatedDate: Date createdDate: Date } export type ICredentialDataDecrypted = ICommonObject export interface IComponentCredentialsPool { [key: string]: INodeCredential } export interface IWalletResponse extends IWallet { balance: string } export interface IVariableDict { [key: string]: string } export interface INodeDependencies { [key: string]: number } export interface INodeDirectedGraph { [key: string]: string[] } export interface IWorkflowExecutedData { nodeLabel: string nodeId: string data: INodeExecutionData[] | IWebhookNodeExecutionData[] status?: ExecutionState } export interface ITestNodeBody { nodeId: string nodes: IReactFlowNode[] edges: IReactFlowEdge[] clientId?: string } export interface IDeployedWorkflowsPool { [key: string]: { emitEventKey?: string abortController?: AbortController workflowExecutedData?: IWorkflowExecutedData[] } } export interface IChildProcessMessage { key: string value?: any } export interface IReactFlowNode { id: string position: { x: number y: number } type: string data: INodeData positionAbsolute: { x: number y: number } z: number handleBounds: { source: any target: any } width: number height: number selected: boolean dragging: boolean } export interface IReactFlowEdge { source: string sourceHandle: string target: string targetHandle: string type: string id: string data: { label: string } } export interface IReactFlowObject { nodes: IReactFlowNode[] edges: IReactFlowEdge[] viewport: { x: number y: number zoom: number } } export interface IRunWorkflowMessageValue { startingNodeIds: string[] componentNodes: IComponentNodesPool reactFlowNodes: IReactFlowNode[] reactFlowEdges: IReactFlowEdge[] graph: INodeDirectedGraph workflowExecutedData: IWorkflowExecutedData[] } export interface IContractRequestBody { credentials: ICommonObject networks: ICommonObject contractInfo: ICommonObject } export interface IWalletRequestBody { name: string network: string providerCredential?: string privateKey?: string } export interface IOAuth2Response { access_token: string token_type: string expires_in: number refresh_token: string } export interface IExploredNode { [key: string]: { remainingLoop: number lastSeenDepth: number } } export interface INodeQueue { nodeId: string depth: number } export type ITestWorkflowBody = ITestNodeBody ================================================ FILE: packages/server/src/NodesPool.ts ================================================ import { IComponentNodesPool } from './Interface' import path from 'path' import { Dirent } from 'fs' import { getNodeModulesPackagePath } from './utils' import { promises } from 'fs' export class NodesPool { componentNodes: IComponentNodesPool = {} /** * Initialize to get all nodes */ async initialize() { const packagePath = getNodeModulesPackagePath('outerbridge-components') const nodesPath = path.join(packagePath, 'dist', 'nodes') const nodeFiles = await this.getFiles(nodesPath) return Promise.all( nodeFiles.map(async (file) => { if (file.endsWith('.js')) { const nodeModule = await import(file) try { const newNodeInstance = new nodeModule.nodeClass() newNodeInstance.filePath = file this.componentNodes[newNodeInstance.name] = newNodeInstance // Replace file icon with absolute path if ( newNodeInstance.icon && (newNodeInstance.icon.endsWith('.svg') || newNodeInstance.icon.endsWith('.png') || newNodeInstance.icon.endsWith('.jpg')) ) { const filePath = file.replace(/\\/g, '/').split('/') filePath.pop() const nodeIconAbsolutePath = `${filePath.join('/')}/${newNodeInstance.icon}` this.componentNodes[newNodeInstance.name].icon = nodeIconAbsolutePath } } catch (e) { // console.error(e); } } }) ) } /** * Recursive function to get node files * @param {string} dir * @returns {string[]} */ async getFiles(dir: string): Promise { const dirents = await promises.readdir(dir, { withFileTypes: true }) const files = await Promise.all( dirents.map((dirent: Dirent) => { const res = path.resolve(dir, dirent.name) return dirent.isDirectory() ? this.getFiles(res) : res }) ) return Array.prototype.concat(...files) } } ================================================ FILE: packages/server/src/commands/start.ts ================================================ import { Command, Flags } from '@oclif/core' import path from 'path' import * as Server from '../index' import * as DataSource from '../DataSource' import dotenv from 'dotenv' dotenv.config({ path: path.join(__dirname, '..', '..', '.env') }) enum EXIT_CODE { SUCCESS = 0, FAILED = 1 } let processExitCode = EXIT_CODE.SUCCESS export default class Start extends Command { static flags = { mongourl: Flags.string() } static args = [] async stopProcess() { console.info('Shutting down Outerbridge...') try { // Shut down the app after timeout if it ever stuck removing pools setTimeout(() => { console.info('Outerbridge was forced to shut down after 30 secs') process.exit(processExitCode) }, 30000) // Removing pools const serverApp = Server.getInstance() if (serverApp) await serverApp.stopApp() } catch (error) { console.error('There was an error shutting down Outerbridge...', error) } process.exit(processExitCode) } async run(): Promise { process.on('SIGTERM', this.stopProcess) process.on('SIGINT', this.stopProcess) // Prevent throw new Error from crashing the app // TODO: Get rid of this and send proper error message to ui process.on('uncaughtException', (err) => { console.error('uncaughtException: ', err) }) const { flags } = await this.parse(Start) if (flags.mongourl) process.env.MONGO_URL = flags.mongourl await (async () => { try { this.log('Starting Outerbridge...') await DataSource.init() await Server.start() } catch (error) { console.error('There was an error starting Outerbridge...', error) processExitCode = EXIT_CODE.FAILED // @ts-ignore process.emit('SIGINT') } })() } } ================================================ FILE: packages/server/src/entity/Contract.ts ================================================ /* eslint-disable */ import { Entity, Column, ObjectIdColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm' import { ObjectId } from 'mongodb' import { IContract } from '../Interface' @Entity() export class Contract implements IContract { @ObjectIdColumn() _id: ObjectId @Column() name: string @Column() abi: string @Column() address: string @Column() network: string @Column('text', { nullable: true }) providerCredential: string @CreateDateColumn() createdDate: Date @UpdateDateColumn() updatedDate: Date } ================================================ FILE: packages/server/src/entity/Credential.ts ================================================ /* eslint-disable */ import { Entity, Column, ObjectIdColumn, Index, CreateDateColumn, UpdateDateColumn } from 'typeorm' import { ObjectId } from 'mongodb' import { ICredential } from '../Interface' @Entity() export class Credential implements ICredential { @ObjectIdColumn() _id: ObjectId @Column() name: string @Index() @Column() nodeCredentialName: string @Column() credentialData: string @CreateDateColumn() createdDate: Date @UpdateDateColumn() updatedDate: Date } ================================================ FILE: packages/server/src/entity/Execution.ts ================================================ /* eslint-disable */ import { Entity, Column, ObjectIdColumn, Index, BeforeInsert, CreateDateColumn } from 'typeorm' import { ObjectId } from 'mongodb' import { shortId } from '../utils' import { IExecution, ExecutionState } from '../Interface' @Entity() export class Execution implements IExecution { @ObjectIdColumn() _id: ObjectId @Index() @Column() shortId: string @BeforeInsert() beforeInsert() { this.shortId = shortId('E', new Date()) } @Column() executionData: string @Column() state: ExecutionState @Column() workflowShortId: string @CreateDateColumn() createdDate: Date @Column({ nullable: true }) stoppedDate: Date } ================================================ FILE: packages/server/src/entity/Wallet.ts ================================================ /* eslint-disable */ import { Entity, Column, ObjectIdColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm' import { ObjectId } from 'mongodb' import { IWallet } from '../Interface' @Entity() export class Wallet implements IWallet { @ObjectIdColumn() _id: ObjectId @Column() name: string @Column() address: string @Column() network: string @Column('text', { nullable: true }) providerCredential: string @Column() walletCredential: string @CreateDateColumn() createdDate: Date @UpdateDateColumn() updatedDate: Date } ================================================ FILE: packages/server/src/entity/Webhook.ts ================================================ /* eslint-disable */ import { Column, Entity, Index, ObjectIdColumn, CreateDateColumn, UpdateDateColumn, PrimaryColumn } from 'typeorm' import { ObjectId } from 'mongodb' import { IWebhook, WebhookMethod } from '../Interface' @Entity() export class Webhook implements IWebhook { @ObjectIdColumn() _id: ObjectId @Column() nodeId: string @Index() @PrimaryColumn() webhookEndpoint: string @Index() @PrimaryColumn() httpMethod: WebhookMethod @Column() workflowShortId: string @Column({ nullable: true }) webhookId: string @CreateDateColumn() createdDate: Date @UpdateDateColumn() updatedDate: Date } ================================================ FILE: packages/server/src/entity/Workflow.ts ================================================ /* eslint-disable */ import { Entity, Column, ObjectIdColumn, Index, BeforeInsert, CreateDateColumn, UpdateDateColumn } from 'typeorm' import { ObjectId } from 'mongodb' import { shortId } from '../utils' import { IWorkflow } from '../Interface' @Entity() export class Workflow implements IWorkflow { @ObjectIdColumn() _id: ObjectId @Index() @Column() shortId: string @BeforeInsert() beforeInsert() { this.shortId = shortId('W', new Date()) } @Column() name: string @Column() flowData: string @Column() deployed: boolean @CreateDateColumn() createdDate: Date @UpdateDateColumn() updatedDate: Date } ================================================ FILE: packages/server/src/index.ts ================================================ import express, { Request, Response } from 'express' import path from 'path' import cors from 'cors' import localtunnel from 'localtunnel' import { ObjectId } from 'mongodb' import { Server, Socket } from 'socket.io' import http from 'http' import { ethers } from 'ethers' import ClientOAuth2 from 'client-oauth2' import { IComponentCredentialsPool, IComponentNodesPool, IContractRequestBody, ICredentialBody, ICredentialDataDecrypted, ICredentialResponse, IReactFlowEdge, IReactFlowNode, IReactFlowObject, ITestNodeBody, ITestWorkflowBody, ITriggerNode, IWalletRequestBody, IWalletResponse, IWebhookNode, IWorkflowExecutedData, IWorkflowResponse, WebhookMethod } from './Interface' import { INodeData, INodeOptionsValue, IDbCollection, etherscanAPIs, nativeCurrency, NETWORK, INodeExecutionData, INode } from 'outerbridge-components' import { CredentialsPool } from './CredentialsPool' import { NodesPool } from './NodesPool' import { decryptCredentialData, getEncryptionKey, processWebhook, decryptCredentials, resolveVariables, transformToCredentialEntity, constructGraphsAndGetStartingNodes, encryptCredentialData, getOAuth2HTMLPath, checkOAuth2TokenRefreshed, constructGraphs, testWorkflow, getNodeModulesPackagePath, getRandomSubdomain, getAPIKeys, addAPIKey, deleteAPIKey, updateAPIKey, updateNodeOutput } from './utils' import { DeployedWorkflowPool } from './DeployedWorkflowPool' import { ActiveTestTriggerPool } from './ActiveTestTriggerPool' import { ActiveTestWebhookPool } from './ActiveTestWebhookPool' import axios, { AxiosRequestConfig } from 'axios' import { Workflow } from './entity/Workflow' import { Execution } from './entity/Execution' import { Credential } from './entity/Credential' import { Webhook } from './entity/Webhook' import { Contract } from './entity/Contract' import { Wallet } from './entity/Wallet' import { getDataSource } from './DataSource' export class App { app: express.Application componentNodes: IComponentNodesPool = {} componentCredentials: IComponentCredentialsPool = {} deployedWorkflowsPool: DeployedWorkflowPool activeTestTriggerPool: ActiveTestTriggerPool activeTestWebhookPool: ActiveTestWebhookPool AppDataSource = getDataSource() constructor() { this.app = express() } async initDatabase() { // Initialize database this.AppDataSource.initialize() .then(async () => { console.info('📦[server]: Data Source has been initialized!') // Initialize localtunnel if (process.env.ENABLE_TUNNEL === 'true') { const subdomain = getRandomSubdomain() const tunnelSettings: localtunnel.TunnelConfig = { subdomain } const port = parseInt(process.env.PORT || '', 10) || 3000 const createTunnel = (timeout: number): Promise => { return new Promise(function (resolve, reject) { localtunnel(port, tunnelSettings).then(resolve, reject) setTimeout(resolve, timeout, 'TUNNEL_TIMED_OUT') }) } const newTunnel = await createTunnel(10000) if (typeof newTunnel !== 'string') { process.env.TUNNEL_BASE_URL = `${newTunnel.url}/` console.info('🌐[server]: TUNNEL_BASE_URL = ', process.env.TUNNEL_BASE_URL) } } // Initialize node instances const nodesPool = new NodesPool() await nodesPool.initialize() this.componentNodes = nodesPool.componentNodes // Initialize credential instances const credsPool = new CredentialsPool() await credsPool.initialize() this.componentCredentials = credsPool.componentCredentials // Initialize deployed worklows instances this.deployedWorkflowsPool = new DeployedWorkflowPool() await this.deployedWorkflowsPool.initialize(this.AppDataSource, this.componentNodes) // Initialize activeTestTriggerPool instance this.activeTestTriggerPool = new ActiveTestTriggerPool() // Initialize activeTestWebhookPool instance this.activeTestWebhookPool = new ActiveTestWebhookPool() // Initialize API keys await getAPIKeys() }) .catch((err) => { console.error('❌[server]: Error during Data Source initialization:', err) }) } async config(io: Server) { // Limit is needed to allow sending/receiving base64 encoded string this.app.use(express.json({ limit: '50mb' })) this.app.use(express.urlencoded({ limit: '50mb', extended: true })) // Allow access from ui when yarn run dev if (process.env.NODE_ENV !== 'production') { this.app.use(cors({ credentials: true, origin: 'http://localhost:8080' })) } // ---------------------------------------- // Workflows // ---------------------------------------- // Get all workflows this.app.get('/api/v1/workflows', async (req: Request, res: Response) => { const workflows: IWorkflowResponse[] = await this.AppDataSource.getMongoRepository(Workflow) .aggregate([ { $lookup: { from: 'execution', localField: 'shortId', foreignField: 'workflowShortId', as: 'execution' } }, { $addFields: { executionCount: { $size: '$execution' } } } ]) .toArray() return res.json(workflows) }) // Get specific workflow via shortId this.app.get('/api/v1/workflows/:shortId', async (req: Request, res: Response) => { const workflows: IWorkflowResponse[] = await this.AppDataSource.getMongoRepository(Workflow) .aggregate([ { $match: { shortId: req.params.shortId } }, { $lookup: { from: 'execution', localField: 'shortId', foreignField: 'workflowShortId', as: 'execution' } }, { $addFields: { executionCount: { $size: '$execution' } } } ]) .toArray() if (workflows.length) return res.json(workflows[0]) return res.status(404).send(`Workflow ${req.params.shortId} not found`) }) // Create new workflow this.app.post('/api/v1/workflows', async (req: Request, res: Response) => { const body = req.body const newWorkflow = new Workflow() Object.assign(newWorkflow, body) const workflow = await this.AppDataSource.getMongoRepository(Workflow).create(newWorkflow) const results = await this.AppDataSource.getMongoRepository(Workflow).save(workflow) const returnWorkflows: IWorkflowResponse[] = await this.AppDataSource.getMongoRepository(Workflow) .aggregate([ { $match: { shortId: results.shortId } }, { $lookup: { from: 'execution', localField: 'shortId', foreignField: 'workflowShortId', as: 'execution' } }, { $addFields: { executionCount: { $size: '$execution' } } } ]) .toArray() if (returnWorkflows.length) return res.json(returnWorkflows[0]) return res.status(404).send(`Workflow ${results.shortId} not found`) }) // Update workflow this.app.put('/api/v1/workflows/:shortId', async (req: Request, res: Response) => { const workflow = await this.AppDataSource.getMongoRepository(Workflow).findOneBy({ shortId: req.params.shortId }) if (!workflow) { res.status(404).send(`Workflow ${req.params.shortId} not found`) return } // If workflow is deployed, remove from deployedWorkflowsPool, then add it again for new changes to be picked up if (workflow.deployed && workflow.flowData) { try { const flowDataString = workflow.flowData const flowData: IReactFlowObject = JSON.parse(flowDataString) const reactFlowNodes = flowData.nodes as IReactFlowNode[] const reactFlowEdges = flowData.edges as IReactFlowEdge[] const workflowShortId = workflow.shortId const response = constructGraphsAndGetStartingNodes(res, reactFlowNodes, reactFlowEdges) if (response === undefined) return const { startingNodeIds } = response await this.deployedWorkflowsPool.remove(startingNodeIds, reactFlowNodes, this.componentNodes, workflowShortId) } catch (e) { return res.status(500).send(e) } } const body = req.body const updateWorkflow = new Workflow() Object.assign(updateWorkflow, body) this.AppDataSource.getMongoRepository(Workflow).merge(workflow, updateWorkflow) const results = await this.AppDataSource.getMongoRepository(Workflow).save(workflow) const returnWorkflows: IWorkflowResponse[] = await this.AppDataSource.getMongoRepository(Workflow) .aggregate([ { $match: { shortId: results.shortId } }, { $lookup: { from: 'execution', localField: 'shortId', foreignField: 'workflowShortId', as: 'execution' } }, { $addFields: { executionCount: { $size: '$execution' } } } ]) .toArray() if (returnWorkflows.length) { const returnWorkflow = returnWorkflows[0] if (returnWorkflow.deployed && returnWorkflow.flowData) { try { const flowData: IReactFlowObject = JSON.parse(returnWorkflow.flowData) const reactFlowNodes = flowData.nodes as IReactFlowNode[] const reactFlowEdges = flowData.edges as IReactFlowEdge[] const workflowShortId = returnWorkflow.shortId const response = constructGraphsAndGetStartingNodes(res, reactFlowNodes, reactFlowEdges) if (response === undefined) return const { graph, startingNodeIds } = response await this.deployedWorkflowsPool.add( startingNodeIds, graph, reactFlowNodes, this.componentNodes, workflowShortId, this.activeTestTriggerPool, this.activeTestWebhookPool ) } catch (e) { return res.status(500).send(e) } } return res.json(returnWorkflow) } return res.status(404).send(`Workflow ${results.shortId} not found`) }) // Delete workflow via shortId this.app.delete('/api/v1/workflows/:shortId', async (req: Request, res: Response) => { const workflow = await this.AppDataSource.getMongoRepository(Workflow).findOneBy({ shortId: req.params.shortId }) if (!workflow) { res.status(404).send(`Workflow ${req.params.shortId} not found`) return } // If workflow is deployed, remove from deployedWorkflowsPool if (workflow.deployed && workflow.flowData) { try { const flowDataString = workflow.flowData const flowData: IReactFlowObject = JSON.parse(flowDataString) const reactFlowNodes = flowData.nodes as IReactFlowNode[] const reactFlowEdges = flowData.edges as IReactFlowEdge[] const workflowShortId = workflow.shortId const response = constructGraphsAndGetStartingNodes(res, reactFlowNodes, reactFlowEdges) if (response === undefined) return const { startingNodeIds } = response await this.deployedWorkflowsPool.remove(startingNodeIds, reactFlowNodes, this.componentNodes, workflowShortId) } catch (e) { return res.status(500).send(e) } } const results = await this.AppDataSource.getMongoRepository(Workflow).delete({ shortId: req.params.shortId }) await this.AppDataSource.getMongoRepository(Webhook).delete({ workflowShortId: req.params.shortId }) await this.AppDataSource.getMongoRepository(Execution).delete({ workflowShortId: req.params.shortId }) return res.json(results) }) // Deploy/Halt workflow via shortId this.app.post('/api/v1/workflows/deploy/:shortId', async (req: Request, res: Response) => { const workflow = await this.AppDataSource.getMongoRepository(Workflow).findOneBy({ shortId: req.params.shortId }) if (!workflow) { res.status(404).send(`Workflow ${req.params.shortId} not found`) return } try { const flowDataString = workflow.flowData const flowData: IReactFlowObject = JSON.parse(flowDataString) const reactFlowNodes = flowData.nodes as IReactFlowNode[] const reactFlowEdges = flowData.edges as IReactFlowEdge[] const workflowShortId = req.params.shortId const haltDeploy = req.body?.halt const response = constructGraphsAndGetStartingNodes(res, reactFlowNodes, reactFlowEdges) if (response === undefined) return const { graph, startingNodeIds } = response if (!haltDeploy) { await this.deployedWorkflowsPool.add( startingNodeIds, graph, reactFlowNodes, this.componentNodes, workflowShortId, this.activeTestTriggerPool, this.activeTestWebhookPool ) } else { await this.deployedWorkflowsPool.remove(startingNodeIds, reactFlowNodes, this.componentNodes, workflowShortId) } const body = { deployed: haltDeploy ? false : true } const updateWorkflow = new Workflow() Object.assign(updateWorkflow, body) this.AppDataSource.getMongoRepository(Workflow).merge(workflow, updateWorkflow) const results = await this.AppDataSource.getMongoRepository(Workflow).save(workflow) const returnWorkflows: IWorkflowResponse[] = await this.AppDataSource.getMongoRepository(Workflow) .aggregate([ { $match: { shortId: results.shortId } }, { $lookup: { from: 'execution', localField: 'shortId', foreignField: 'workflowShortId', as: 'execution' } }, { $addFields: { executionCount: { $size: '$execution' } } } ]) .toArray() if (returnWorkflows.length) return res.json(returnWorkflows[0]) return res.status(404).send(`Workflow ${results.shortId} not found`) } catch (e) { res.status(500).send(`Workflow ${req.params.shortId} deploy error: ${e}`) return } }) // Test Workflow from a starting point to end this.app.post('/api/v1/workflows/test/:startingNodeId', async (req: Request, res: Response) => { const body = req.body as ITestWorkflowBody const nodes = body.nodes || [] const edges = body.edges || [] const clientId = body.clientId || '' const { graph } = constructGraphs(nodes, edges) const startingNodeId = req.params.startingNodeId const startNode = nodes.find((nd: IReactFlowNode) => nd.id === startingNodeId) if (startNode && startNode.data) { let nodeData = startNode.data await decryptCredentials(nodeData) const nodeDataArray = resolveVariables(nodeData, nodes) nodeData = nodeDataArray[0] if (!Object.prototype.hasOwnProperty.call(this.componentNodes, nodeData.name)) { res.status(404).send(`Unable to test workflow from node: ${nodeData.name}`) return } if (nodeData.type === 'trigger') { const triggerNodeInstance = this.componentNodes[nodeData.name] as ITriggerNode const emitEventKey = startingNodeId nodeData.emitEventKey = emitEventKey triggerNodeInstance.once(emitEventKey, async (result: INodeExecutionData[]) => { await this.activeTestTriggerPool.remove(nodeData.name, this.componentNodes) const newWorkflowExecutedData = { nodeId: startingNodeId, nodeLabel: nodeData.label, data: result, status: 'FINISHED' } as IWorkflowExecutedData io.to(clientId).emit('testWorkflowNodeResponse', newWorkflowExecutedData) testWorkflow(startingNodeId, result, nodes, edges, graph, this.componentNodes, clientId, io) }) await triggerNodeInstance.runTrigger!.call(triggerNodeInstance, nodeData) this.activeTestTriggerPool.add(nodeData.name, nodeData) } else if (nodeData.type === 'webhook') { const webhookNodeInstance = this.componentNodes[nodeData.name] as IWebhookNode const newBody = { webhookEndpoint: nodeData.webhookEndpoint, httpMethod: (nodeData.inputParameters?.httpMethod as WebhookMethod) || 'POST' } as any if (webhookNodeInstance.webhookMethods?.createWebhook) { if (!process.env.TUNNEL_BASE_URL) { res.status(500).send(`Please enable tunnel by setting ENABLE_TUNNEL to true in env file`) return } const webhookFullUrl = `${process.env.TUNNEL_BASE_URL}api/v1/webhook/${nodeData.webhookEndpoint}` const webhookId = await webhookNodeInstance.webhookMethods?.createWebhook.call( webhookNodeInstance, nodeData, webhookFullUrl ) if (webhookId !== undefined) { newBody.webhookId = webhookId } } this.activeTestWebhookPool.add( newBody.webhookEndpoint, newBody.httpMethod, nodes, edges, nodeData, startingNodeId, clientId, true, newBody?.webhookId ) } else if (nodeData.type === 'action') { const actionNodeInstance = this.componentNodes[nodeData.name] as INode const result = await actionNodeInstance.run!.call(actionNodeInstance, nodeData) checkOAuth2TokenRefreshed(result, nodeData) const newWorkflowExecutedData = { nodeId: startingNodeId, nodeLabel: nodeData.label, data: result, status: 'FINISHED' } as IWorkflowExecutedData io.to(clientId).emit('testWorkflowNodeResponse', newWorkflowExecutedData) const reactFlowNodes = nodes const nodeIndex = reactFlowNodes.findIndex((nd) => nd.id === startingNodeId) updateNodeOutput(reactFlowNodes, nodeIndex, result || []) testWorkflow(startingNodeId, result || [], reactFlowNodes, edges, graph, this.componentNodes, clientId, io) } } }) // ---------------------------------------- // Execution // ---------------------------------------- // Get all executions this.app.get('/api/v1/executions', async (req: Request, res: Response) => { const executions = await this.AppDataSource.getMongoRepository(Execution).find() return res.json(executions) }) // Get specific execution via shortId this.app.get('/api/v1/executions/:shortId', async (req: Request, res: Response) => { const results = await this.AppDataSource.getMongoRepository(Execution).findOneBy({ shortId: req.params.shortId }) return res.json(results) }) // Create new execution this.app.post('/api/v1/executions', async (req: Request, res: Response) => { const body = req.body const newExecution = new Execution() Object.assign(newExecution, body) const execution = await this.AppDataSource.getMongoRepository(Execution).create(newExecution) const results = await this.AppDataSource.getMongoRepository(Execution).save(execution) return res.json(results) }) // Update execution this.app.put('/api/v1/executions/:shortId', async (req: Request, res: Response) => { const execution = await this.AppDataSource.getMongoRepository(Execution).findOneBy({ shortId: req.params.shortId }) if (!execution) { res.status(404).send(`Execution ${req.params.shortId} not found`) return } const body = req.body const updateExecution = new Execution() Object.assign(updateExecution, body) this.AppDataSource.getMongoRepository(Execution).merge(execution, updateExecution) const results = await this.AppDataSource.getMongoRepository(Execution).save(execution) return res.json(results) }) // Delete execution via shortId this.app.delete('/api/v1/executions/:shortId', async (req: Request, res: Response) => { const results = await this.AppDataSource.getMongoRepository(Execution).delete({ shortId: req.params.shortId }) return res.json(results) }) // ---------------------------------------- // Nodes // ---------------------------------------- // Get all component nodes this.app.get('/api/v1/nodes', (req: Request, res: Response) => { const returnData = [] for (const nodeName in this.componentNodes) { // Remove certain node properties to avoid error of Converting circular structure to JSON const clonedNode = JSON.parse( JSON.stringify(this.componentNodes[nodeName], (key, val) => { if (key !== 'cronJobs' && key !== 'providers') return val }) ) returnData.push(clonedNode) } return res.json(returnData) }) // Get specific component node via name this.app.get('/api/v1/nodes/:name', (req: Request, res: Response) => { if (Object.prototype.hasOwnProperty.call(this.componentNodes, req.params.name)) { // Remove certain node properties to avoid error of Converting circular structure to JSON const clonedNode = JSON.parse( JSON.stringify(this.componentNodes[req.params.name], (key, val) => { if (key !== 'cronJobs' && key !== 'providers') return val }) ) return res.json(clonedNode) } else { throw new Error(`Node ${req.params.name} not found`) } }) // Returns specific component node icon via name this.app.get('/api/v1/node-icon/:name', (req: Request, res: Response) => { if (Object.prototype.hasOwnProperty.call(this.componentNodes, req.params.name)) { const nodeInstance = this.componentNodes[req.params.name] if (nodeInstance.icon === undefined) { throw new Error(`Node ${req.params.name} icon not found`) } if (nodeInstance.icon.endsWith('.svg') || nodeInstance.icon.endsWith('.png') || nodeInstance.icon.endsWith('.jpg')) { const filepath = nodeInstance.icon res.sendFile(filepath) } else { throw new Error(`Node ${req.params.name} icon is missing icon`) } } else { throw new Error(`Node ${req.params.name} not found`) } }) // Test a node this.app.post('/api/v1/node-test/:name', async (req: Request, res: Response) => { const body: ITestNodeBody = req.body const { nodes, edges, nodeId, clientId } = body const node = nodes.find((nd: IReactFlowNode) => nd.id === nodeId) if (!node) return res.status(404).send(`Test node ${nodeId} not found`) if (Object.prototype.hasOwnProperty.call(this.componentNodes, req.params.name)) { try { const nodeInstance = this.componentNodes[req.params.name] const nodeType = nodeInstance.type const nodeData = node.data await decryptCredentials(nodeData) if (nodeType === 'action') { let results: INodeExecutionData[] = [] const reactFlowNodeData: INodeData[] = resolveVariables(nodeData, nodes) for (let i = 0; i < reactFlowNodeData.length; i += 1) { const result = await nodeInstance.run!.call(nodeInstance, reactFlowNodeData[i]) checkOAuth2TokenRefreshed(result, reactFlowNodeData[i]) if (result) results.push(...result) } return res.json(results) } else if (nodeType === 'trigger') { const triggerNodeInstance = nodeInstance as ITriggerNode const emitEventKey = nodeId nodeData.emitEventKey = emitEventKey triggerNodeInstance.once(emitEventKey, async (result: INodeExecutionData[]) => { await this.activeTestTriggerPool.remove(nodeData.name, this.componentNodes) return res.json(result) }) await triggerNodeInstance.runTrigger!.call(triggerNodeInstance, nodeData) this.activeTestTriggerPool.add(req.params.name, nodeData) } else if (nodeType === 'webhook') { const webhookNodeInstance = nodeInstance as IWebhookNode const newBody = { webhookEndpoint: nodeData.webhookEndpoint, httpMethod: (nodeData.inputParameters?.httpMethod as WebhookMethod) || 'POST' } as any if (webhookNodeInstance.webhookMethods?.createWebhook) { if (!process.env.TUNNEL_BASE_URL) { res.status(500).send(`Please enable tunnel by setting ENABLE_TUNNEL to true in env file`) return } const webhookFullUrl = `${process.env.TUNNEL_BASE_URL}api/v1/webhook/${nodeData.webhookEndpoint}` const webhookId = await webhookNodeInstance.webhookMethods?.createWebhook.call( webhookNodeInstance, nodeData, webhookFullUrl ) if (webhookId !== undefined) { newBody.webhookId = webhookId } } this.activeTestWebhookPool.add( newBody.webhookEndpoint, newBody.httpMethod, nodes, edges, nodeData, nodeId, clientId as string, false, newBody?.webhookId ) return res.json(newBody) } } catch (error) { res.status(500).send(`Node test error: ${error}`) console.error(error) return } } else { res.status(404).send(`Node ${req.params.name} not found`) return } }) // load async options this.app.post('/api/v1/node-load-method/:name', async (req: Request, res: Response) => { const nodeData: INodeData = req.body if (Object.prototype.hasOwnProperty.call(this.componentNodes, req.params.name)) { try { const nodeInstance = this.componentNodes[req.params.name] const methodName = nodeData.loadMethod || '' const loadFromDbCollections = nodeData.loadFromDbCollections || [] const dbCollection = {} as IDbCollection const apiKeys = await getAPIKeys() for (let i = 0; i < loadFromDbCollections.length; i += 1) { let collection: any if (loadFromDbCollections[i] === 'Contract') collection = Contract else if (loadFromDbCollections[i] === 'Workflow') collection = Workflow else if (loadFromDbCollections[i] === 'Webhook') collection = Webhook else if (loadFromDbCollections[i] === 'Execution') collection = Execution else if (loadFromDbCollections[i] === 'Credential') collection = Credential else if (loadFromDbCollections[i] === 'Wallet') collection = Wallet const res = await this.AppDataSource.getMongoRepository(collection).find() dbCollection[loadFromDbCollections[i]] = res } await decryptCredentials(nodeData) const returnOptions: INodeOptionsValue[] = await nodeInstance.loadMethods![methodName]!.call( nodeInstance, nodeData, loadFromDbCollections.length ? dbCollection : undefined, apiKeys ) return res.json(returnOptions) } catch (error) { return res.json([]) } } else { res.status(404).send(`Node ${req.params.name} not found`) return } }) // ---------------------------------------- // Credential // ---------------------------------------- // Create new credential this.app.post('/api/v1/credentials', async (req: Request, res: Response) => { const body: ICredentialBody = req.body const newCredential = await transformToCredentialEntity(body) const credential = await this.AppDataSource.getMongoRepository(Credential).create(newCredential) const results = await this.AppDataSource.getMongoRepository(Credential).save(credential) return res.json(results) }) // Get node credential via specific nodeCredentialName this.app.get('/api/v1/node-credentials/:nodeCredentialName', (req: Request, res: Response) => { const credentials = [] for (const credName in this.componentCredentials) { credentials.push(this.componentCredentials[credName]) } const cred = credentials.find((crd) => crd.name === req.params.nodeCredentialName) if (cred === undefined) { throw new Error(`Credential ${req.params.nodeCredentialName} not found`) } return res.json(cred) }) // Get list of registered credentials via nodeCredentialName this.app.get('/api/v1/credentials', async (req: Request, res: Response) => { const credentials = await this.AppDataSource.getMongoRepository(Credential).find({ // @ts-ignore where: { nodeCredentialName: { $eq: req.query.nodeCredentialName } } }) return res.json(credentials) }) // Get registered credential via objectId this.app.get('/api/v1/credentials/:id', async (req: Request, res: Response) => { const isEncrypted = req.query.isEncrypted const encryptKey = await getEncryptionKey() const credential = await this.AppDataSource.getMongoRepository(Credential).findOneBy({ _id: new ObjectId(req.params.id) }) if (!credential) { res.status(404).send(`Credential ${req.params.id} not found`) return } if (isEncrypted) { return res.json(credential) } else { // Decrpyt credentialData const decryptedCredentialData = decryptCredentialData(credential.credentialData, encryptKey) const credentialResponse: ICredentialResponse = { ...credential, credentialData: decryptedCredentialData } return res.json(credentialResponse) } }) // Update credential this.app.put('/api/v1/credentials/:id', async (req: Request, res: Response) => { const credential = await this.AppDataSource.getMongoRepository(Credential).findOneBy({ _id: new ObjectId(req.params.id) }) if (!credential) { res.status(404).send(`Credential ${req.params.id} not found`) return } const encryptKey = await getEncryptionKey() const decryptedCredentialData = decryptCredentialData(credential.credentialData, encryptKey) const body: ICredentialBody = req.body const { credentialData, name, nodeCredentialName } = body const newBody: ICredentialBody = { name: name, nodeCredentialName: nodeCredentialName, credentialData: { ...decryptedCredentialData, ...credentialData } } const updateCredential = await transformToCredentialEntity(newBody) this.AppDataSource.getMongoRepository(Credential).merge(credential, updateCredential) const results = await this.AppDataSource.getMongoRepository(Credential).save(credential) return res.json(results) }) // Delete credential via id this.app.delete('/api/v1/credentials/:id', async (req: Request, res: Response) => { const results = await this.AppDataSource.getMongoRepository(Credential).deleteOne({ _id: new ObjectId(req.params.id) }) return res.json(results) }) // ---------------------------------------- // Contract // ---------------------------------------- // Get all contracts this.app.get('/api/v1/contracts', async (req: Request, res: Response) => { const contracts = await this.AppDataSource.getMongoRepository(Contract).find() return res.json(contracts) }) // Get specific contract via id this.app.get('/api/v1/contracts/:id', async (req: Request, res: Response) => { const results = await this.AppDataSource.getMongoRepository(Contract).findOneBy({ _id: new ObjectId(req.params.id) }) return res.json(results) }) // Create new contract this.app.post('/api/v1/contracts', async (req: Request, res: Response) => { try { const body = req.body const newContract = new Contract() Object.assign(newContract, body) const contract = await this.AppDataSource.getMongoRepository(Contract).create(newContract) const results = await this.AppDataSource.getMongoRepository(Contract).save(contract) return res.json(results) } catch (e) { return res.status(500).send(e) } }) // Update contract this.app.put('/api/v1/contracts/:id', async (req: Request, res: Response) => { const contract = await this.AppDataSource.getMongoRepository(Contract).findOneBy({ _id: new ObjectId(req.params.id) }) if (!contract) { res.status(404).send(`Contract with id: ${req.params.id} not found`) return } const body = req.body const updateContract = new Contract() Object.assign(updateContract, body) this.AppDataSource.getMongoRepository(Contract).merge(contract, updateContract) try { const results = await this.AppDataSource.getMongoRepository(Contract).save(contract) return res.json(results) } catch (e) { return res.status(500).send(e) } }) // Delete contract via id this.app.delete('/api/v1/contracts/:id', async (req: Request, res: Response) => { const deletQuery = { _id: new ObjectId(req.params.id) } as any const results = await this.AppDataSource.getMongoRepository(Contract).delete(deletQuery) return res.json(results) }) // Get contract ABI this.app.post('/api/v1/contracts/getabi', async (req: Request, res: Response) => { const body: IContractRequestBody = req.body if (body.networks === undefined || body.credentials === undefined || body.contractInfo === undefined) { res.status(500).send(`Missing contract details`) return } if (body.credentials && body.credentials.registeredCredential) { // @ts-ignore const credentialId: string = body.credentials.registeredCredential?._id const credential = await this.AppDataSource.getMongoRepository(Credential).findOneBy({ _id: new ObjectId(credentialId) }) if (!credential) return res.status(404).send(`Credential with id: ${credentialId} not found`) const encryptKey = await getEncryptionKey() const decryptedCredentialData = decryptCredentialData(credential.credentialData, encryptKey) body.credentials = decryptedCredentialData } let url = '' // Get ABI if (body.credentials.apiKey) { url = `${body.networks.uri}?module=contract&action=getabi&address=${body.contractInfo.address}&apikey=${body.credentials.apiKey}` } else { url = `${body.networks.uri}?module=contract&action=getabi&address=${body.contractInfo.address}` } // URL is not etherscan.io subdomain if (new URL(url).hostname.split('.').slice(-2).join('.') !== 'etherscan.io') { // How do you want to handle this error? return res.status(403).json({ status: '0', message: 'NOTOK', result: 'Please change URL and try again' }) } const options: AxiosRequestConfig = { method: 'GET', url } try { const response = await axios.request(options) return res.json(response.data) } catch (e) { console.error(e) const errorObject = { status: '0', message: 'NOTOK', result: 'Unable to fetch ABI, please use correct API Key for higher rate limit' } return res.json(errorObject) } }) // ---------------------------------------- // Wallets // ---------------------------------------- // Get all wallets this.app.get('/api/v1/wallets', async (req: Request, res: Response) => { const wallets = await this.AppDataSource.getMongoRepository(Wallet).find() return res.json(wallets) }) // Get specific wallet via id this.app.get('/api/v1/wallets/:id', async (req: Request, res: Response) => { try { const wallet = await this.AppDataSource.getMongoRepository(Wallet).findOneBy({ _id: new ObjectId(req.params.id) }) if (!wallet) { res.status(404).send(`Wallet ${req.params.id} not found`) return } const walletResponse: IWalletResponse = { ...wallet, balance: '' } // Decrpyt providerCredential const encryptKey = await getEncryptionKey() const providerCredential = JSON.parse(wallet.providerCredential) let decryptedCredentialData: ICredentialDataDecrypted = {} if (providerCredential.registeredCredential) { const credentialId: string = providerCredential.registeredCredential?._id const credential = await this.AppDataSource.getMongoRepository(Credential).findOneBy({ _id: new ObjectId(credentialId) }) if (!credential) return res.status(404).send(`Credential with id: ${credentialId} not found`) // Decrpyt credentialData decryptedCredentialData = decryptCredentialData(credential.credentialData, encryptKey) } const credentialMethod = providerCredential.credentialMethod let url = '' const network = wallet.network as NETWORK // Get Balance if (decryptedCredentialData.apiKey && credentialMethod !== 'noAuth') { url = `${etherscanAPIs[network]}?module=account&action=balance&address=${wallet.address}&tag=latest&apikey=${ decryptedCredentialData.apiKey as string }` } else { url = `${etherscanAPIs[network]}?module=account&action=balance&address=${wallet.address}&tag=latest` } const options: AxiosRequestConfig = { method: 'GET', url } try { const response = await axios.request(options) if (response.data && response.data.result) { walletResponse.balance = `${ethers.utils.formatEther(ethers.BigNumber.from(response.data.result))} ${ nativeCurrency[wallet.network as NETWORK] }` } } catch (e) { walletResponse.balance = 'Unable to fetch balance, please use correct API Key for higher rate limit' } return res.json(walletResponse) } catch (e) { console.error(e) return res.status(500).send(e) } }) // Create new wallet this.app.post('/api/v1/wallets', async (req: Request, res: Response) => { try { const body: IWalletRequestBody = req.body const { name, network, providerCredential, privateKey } = body const encryptKey = await getEncryptionKey() const newBody: any = { name, network, providerCredential } let randomWallet: ethers.Wallet if (privateKey) randomWallet = new ethers.Wallet(privateKey) else randomWallet = ethers.Wallet.createRandom() newBody.address = randomWallet.address const walletCredential = { privateKey: randomWallet.privateKey } as any // Imported wallet doesn't have mnemonic and path if (!privateKey) { walletCredential.mnemonic = randomWallet.mnemonic.phrase walletCredential.path = randomWallet.mnemonic.path } newBody.walletCredential = encryptCredentialData(walletCredential, encryptKey) as string const newWallet = new Wallet() Object.assign(newWallet, newBody) const wallet = await this.AppDataSource.getMongoRepository(Wallet).create(newWallet) const results = await this.AppDataSource.getMongoRepository(Wallet).save(wallet) return res.json(results) } catch (e) { return res.status(500).send(e) } }) // Update wallet this.app.put('/api/v1/wallets/:id', async (req: Request, res: Response) => { const wallet = await this.AppDataSource.getMongoRepository(Wallet).findOneBy({ _id: new ObjectId(req.params.id) }) if (!wallet) { res.status(404).send(`Wallet with id: ${req.params.id} not found`) return } const body = req.body const updateWallet = new Wallet() Object.assign(updateWallet, body) this.AppDataSource.getMongoRepository(Wallet).merge(wallet, updateWallet) try { const results = await this.AppDataSource.getMongoRepository(Wallet).save(wallet) return res.json(results) } catch (e) { return res.status(500).send(e) } }) // Delete wallet via id this.app.delete('/api/v1/wallets/:id', async (req: Request, res: Response) => { const deletQuery = { _id: new ObjectId(req.params.id) } as any const results = await this.AppDataSource.getMongoRepository(Wallet).delete(deletQuery) return res.json(results) }) // Get wallet credentials this.app.get('/api/v1/wallets/credential/:id', async (req: Request, res: Response) => { try { const wallet = await this.AppDataSource.getMongoRepository(Wallet).findOneBy({ _id: new ObjectId(req.params.id) }) if (!wallet) { res.status(404).send(`Wallet ${req.params.id} not found`) return } // Decrpyt credentialData const encryptKey = await getEncryptionKey() const decryptedCredentialData = decryptCredentialData(wallet.walletCredential, encryptKey) return res.json(decryptedCredentialData) } catch (e) { console.error(e) return res.status(500).send(e) } }) // ---------------------------------------- // Active Test Pools // ---------------------------------------- // Remove active test triggers this.app.post('/api/v1/remove-test-triggers', async (req: Request, res: Response) => { if (this.activeTestTriggerPool) await this.activeTestTriggerPool.removeAll(this.componentNodes) res.status(200).send('success') return }) // Remove active test webhooks this.app.post('/api/v1/remove-test-webhooks', async (req: Request, res: Response) => { if (this.activeTestWebhookPool) await this.activeTestWebhookPool.removeAll(this.componentNodes) res.status(200).send('success') return }) // ---------------------------------------- // API Keys // ---------------------------------------- // Get api keys this.app.get('/api/v1/apikey', async (req: Request, res: Response) => { const keys = await getAPIKeys() return res.json(keys) }) // Add new api key this.app.post('/api/v1/apikey', async (req: Request, res: Response) => { const keys = await addAPIKey(req.body.keyName) return res.json(keys) }) // Update api key this.app.put('/api/v1/apikey/:id', async (req: Request, res: Response) => { const keys = await updateAPIKey(req.params.id, req.body.keyName) return res.json(keys) }) // Delete new api key this.app.delete('/api/v1/apikey/:id', async (req: Request, res: Response) => { const keys = await deleteAPIKey(req.params.id) return res.json(keys) }) // ---------------------------------------- // Webhook // ---------------------------------------- // GET webhook requests this.app.get(`/api/v1/webhook/*`, express.raw(), async (req: Request, res: Response) => { const splitUrl = req.path.split('/api/v1/webhook/') const webhookEndpoint = splitUrl[splitUrl.length - 1] await processWebhook( res, req, this.AppDataSource, webhookEndpoint, 'GET', this.componentNodes, io, this.deployedWorkflowsPool, this.activeTestWebhookPool ) }) // POST webhook requests this.app.post(`/api/v1/webhook/*`, express.raw(), async (req: Request, res: Response) => { const splitUrl = req.path.split('/api/v1/webhook/') const webhookEndpoint = splitUrl[splitUrl.length - 1] await processWebhook( res, req, this.AppDataSource, webhookEndpoint, 'POST', this.componentNodes, io, this.deployedWorkflowsPool, this.activeTestWebhookPool ) }) this.app.get('/api/v1/get-tunnel-url', (req: Request, res: Response) => { if (!process.env.TUNNEL_BASE_URL) throw new Error(`Tunnel URL not found`) res.send(process.env.TUNNEL_BASE_URL) }) // ---------------------------------------- // OAuth2 // ---------------------------------------- this.app.get('/api/v1/oauth2', async (req: Request, res: Response) => { if (!req.query.credentialId) return res.status(404).send('Credential not found') const credentialId = req.query.credentialId const credential = await this.AppDataSource.getMongoRepository(Credential).findOneBy({ _id: new ObjectId(credentialId as string) }) if (!credential) return res.status(404).send(`Credential with Id ${credentialId} not found`) // Decrpyt credentialData const encryptKey = await getEncryptionKey() const decryptedCredentialData = decryptCredentialData(credential.credentialData, encryptKey) const baseURL = req.get('host') const authUrl = decryptedCredentialData.authUrl as string const authorizationURLParameters = decryptedCredentialData.authorizationURLParameters as string const clientID = decryptedCredentialData.clientID as string const scope = decryptedCredentialData.scope as string let scopeArray: any try { scopeArray = scope.replace(/\s/g, '') scopeArray = JSON.parse(scopeArray) } catch (e) { return res.status(500).send(e) } // check if authUrl is valid and is http/https try { const authUrlCheck = new URL(authUrl) if (authUrlCheck.protocol !== 'http:' && authUrlCheck.protocol !== 'https:') { throw new Error('Invalid URL Protocol') } } catch (e) { return res.status(500).send(e) } const serializedScope = scopeArray.join(' ') const redirectUrl = `${req.secure ? 'https' : req.protocol}://${baseURL}/api/v1/oauth2/callback` const returnURL = `${authUrl}?${authorizationURLParameters}&client_id=${clientID}&scope=${serializedScope}&redirect_uri=${redirectUrl}&state=${credentialId}` res.send(returnURL) }) this.app.get('/api/v1/oauth2/callback', async (req: Request, res: Response) => { const code = req.query.code if (!code) return res.status(500).send('Unable to retrieve authorization code from oAuth2 callback') const credentialId = req.query.state if (!credentialId) return res.status(500).send('Unable to retrieve credentialId from oAuth2 callback') const credential = await this.AppDataSource.getMongoRepository(Credential).findOneBy({ _id: new ObjectId(credentialId as string) }) if (!credential) return res.status(404).send(`Credential with Id ${credentialId} not found`) // Decrpyt credentialData const encryptKey = await getEncryptionKey() const decryptedCredentialData = decryptCredentialData(credential.credentialData, encryptKey) // Get access_token and refresh_token const accessTokenUrl = decryptedCredentialData.accessTokenUrl as string const client_id = decryptedCredentialData.clientID as string const client_secret = decryptedCredentialData.clientSecret as string | undefined const authUrl = decryptedCredentialData.authUrl as string const scope = decryptedCredentialData.scope as string let scopeArray: string[] = [] try { scopeArray = JSON.parse(scope.replace(/\s/g, '')) } catch (e) { return res.status(500).send(e) } const baseURL = req.get('host') const redirect_uri = `${req.secure ? 'https' : req.protocol}://${baseURL}/api/v1/oauth2/callback` const oAuth2Parameters = { clientId: client_id, clientSecret: client_secret, accessTokenUri: accessTokenUrl, authorizationUri: authUrl, redirectUri: redirect_uri, scopes: scopeArray } const oAuthObj = new ClientOAuth2(oAuth2Parameters) const queryParameters = req.originalUrl.split('?').splice(1, 1).join('') const oauthToken = await oAuthObj.code.getToken(`${oAuth2Parameters.redirectUri}?${queryParameters}`) const { access_token, token_type, expires_in, refresh_token } = oauthToken.data const body: ICredentialBody = { name: credential.name, nodeCredentialName: credential.nodeCredentialName, credentialData: { ...decryptedCredentialData, access_token, token_type, expires_in, refresh_token } } const updateCredential = await transformToCredentialEntity(body) this.AppDataSource.getMongoRepository(Credential).merge(credential, updateCredential) await this.AppDataSource.getMongoRepository(Credential).save(credential) return res.sendFile(getOAuth2HTMLPath()) }) this.app.get('/api/v1/oauth2-redirecturl', async (req: Request, res: Response) => { const baseURL = req.get('host') res.send(`${req.secure ? 'https' : req.protocol}://${baseURL}/api/v1/oauth2/callback`) }) // ---------------------------------------- // Serve UI static // ---------------------------------------- const packagePath = getNodeModulesPackagePath('outerbridge-ui') const uiBuildPath = path.join(packagePath, 'build') const uiHtmlPath = path.join(packagePath, 'build', 'index.html') this.app.use('/', express.static(uiBuildPath)) // All other requests not handled will return React app this.app.use((req, res) => { res.sendFile(uiHtmlPath) }) } async stopApp() { try { const removePromises: any[] = [] // Remove deployed workflows pools if (this.deployedWorkflowsPool) removePromises.push(this.deployedWorkflowsPool.removeAll(this.componentNodes)) // Remove test trigger pools if (this.activeTestTriggerPool) removePromises.push(this.activeTestTriggerPool.removeAll(this.componentNodes)) // Remove test webhook pools if (this.activeTestWebhookPool) removePromises.push(this.activeTestWebhookPool.removeAll(this.componentNodes)) await Promise.all(removePromises) } catch (e) { console.error(`❌[server]: Outerbridge Server shut down error: ${e}`) } } } let serverApp: App | undefined export async function start(): Promise { serverApp = new App() const port = parseInt(process.env.PORT || '', 10) || 3000 const server = http.createServer(serverApp.app) const io = new Server(server, { cors: { origin: 'http://localhost:8080' } }) io.on('connection', (socket: Socket) => { console.info('👥[server]: client connected: ', socket.id) socket.on('disconnect', (reason) => { console.info('👤[server]: client disconnected = ', reason) }) }) await serverApp.initDatabase() await serverApp.config(io) server.listen(port, () => { console.info(`⚡️[server]: Outerbridge Server is listening at ${port}`) }) } export function getInstance(): App | undefined { return serverApp } ================================================ FILE: packages/server/src/utils/index.ts ================================================ import moment from 'moment' import path from 'path' import fs from 'fs' import { lib, PBKDF2, AES, enc } from 'crypto-js' import { Request, Response } from 'express' import { DataSource } from 'typeorm' import { ICredentialBody, ICredentialDataDecrypted, INodeDependencies, INodeDirectedGraph, IReactFlowEdge, IReactFlowNode, IVariableDict, IReactFlowObject, IWebhookNode, IComponentNodesPool, WebhookMethod, INodeQueue, IExploredNode, IWorkflowExecutedData } from '../Interface' import lodash from 'lodash' import { ICommonObject, INodeData, INodeExecutionData, IWallet, OAUTH2_REFRESHED, IOAuth2RefreshResponse } from 'outerbridge-components' import { Workflow } from '../entity/Workflow' import { Credential } from '../entity/Credential' import { Webhook } from '../entity/Webhook' import { DeployedWorkflowPool } from '../DeployedWorkflowPool' import { ObjectId } from 'mongodb' import { ActiveTestWebhookPool } from '../ActiveTestWebhookPool' import { getDataSource } from '../DataSource' import { scryptSync, randomBytes, timingSafeEqual } from 'crypto' export enum ShortIdConstants { WORKFLOW_ID_PREFIX = 'W', EXECUTION_ID_PREFIX = 'E' } const RANDOM_LENGTH = 8 const DICTIONARY_1 = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ' const DICTIONARY_3 = 'abcdefghijklmnopqrstuvwxyz0123456789' /** * Returns a Short ID * Format : WDDMMMYY-[0-1A-Z]*8 , ie: B10JAN21-2CH9PX8N * Where W=Entity Prefix, DD=DAY, MMM=Month, YY=Year, -=Separator (hyphen character), [0-1A-Z]*8 = random part of length 8 by default. * * @param {string | Date} prefix Identifies the Entity, 'W' for Workflow, 'E' for Execution * @param {Date} date The Date the ShortId was created * @returns {string} shortId */ export const shortId = (prefix: 'W' | 'E', date: string | Date): string => { const isValidPrefix = prefix === 'W' || prefix === 'E' const utcCreatedAt = new Date(date) if (!isValidPrefix) throw new Error('Invalid short id prefix, only possible values "W" or "E".') const DICTIONARY = DICTIONARY_1 let randomPart = '' for (let i = 0; i < RANDOM_LENGTH; i++) { randomPart += getRandomCharFromDictionary(DICTIONARY) } const sanitizedDate = formatDateForShortID(utcCreatedAt) return `${prefix}${sanitizedDate}-${randomPart}` } /** * Format a date for use in the short id DDMMMYY with no hyphens * @param {Date} date * @returns {string} the sanitized date as string ie: 10JAN21 */ export const formatDateForShortID = (date: Date): string => { const localDate = moment(date) return localDate.format('DDMMMYY').toUpperCase() } export const getRandomCharFromDictionary = (dictionary: string) => { const minDec = 0 const maxDec = dictionary.length + 1 const randDec = Math.floor(Math.random() * (maxDec - minDec) + minDec) return dictionary.charAt(randDec) } export const getRandomSubdomain = () => { let randomPart = '' for (let i = 0; i < 24; i++) { randomPart += getRandomCharFromDictionary(DICTIONARY_3) } return randomPart } /** * Returns the path of node modules package * @param {string} packageName * @returns {string} */ export const getNodeModulesPackagePath = (packageName: string): string => { const checkPaths = [ path.join(__dirname, '..', 'node_modules', packageName), path.join(__dirname, '..', '..', 'node_modules', packageName), path.join(__dirname, '..', '..', '..', 'node_modules', packageName), path.join(__dirname, '..', '..', '..', '..', 'node_modules', packageName), path.join(__dirname, '..', '..', '..', '..', '..', 'node_modules', packageName) ] for (const checkPath of checkPaths) { if (fs.existsSync(checkPath)) { return checkPath } } return '' } /** * Returns the path of encryption key * @returns {string} */ export const getEncryptionKeyPath = (): string => { return path.join(__dirname, '..', '..', 'encryption.key') } /** * Generate an encryption key * @returns {string} */ export const generateEncryptKey = (): string => { const salt = lib.WordArray.random(128 / 8) const key256Bits = PBKDF2(process.env.PASSPHRASE || 'MYPASSPHRASE', salt, { keySize: 256 / 32, iterations: 1000 }) return key256Bits.toString() } /** * Returns the encryption key * @returns {string} */ export const getEncryptionKey = async (): Promise => { try { return await fs.promises.readFile(getEncryptionKeyPath(), 'utf8') } catch (error) { const encryptKey = generateEncryptKey() await fs.promises.writeFile(getEncryptionKeyPath(), encryptKey) return encryptKey } } /** * Returns the api key path * @returns {string} */ export const getAPIKeyPath = (): string => { return path.join(__dirname, '..', '..', 'api.json') } /** * Generate the api key * @returns {string} */ export const generateAPIKey = () => { const buffer = randomBytes(32) return buffer.toString('base64') } /** * Generate the secret key * @param {string} apiKey * @returns {string} */ export const generateSecretHash = (apiKey: string) => { const salt = randomBytes(8).toString('hex') const buffer = scryptSync(apiKey, salt, 64) as Buffer return `${buffer.toString('hex')}.${salt}` } /** * Verify valid keys * @param {string} storedKey * @param {string} suppliedKey * @returns {boolean} */ export const compareKeys = (storedKey: string, suppliedKey: string) => { const [hashedPassword, salt] = storedKey.split('.') const buffer = scryptSync(suppliedKey, salt, 64) as Buffer return timingSafeEqual(Buffer.from(hashedPassword, 'hex'), buffer) } /** * Get API keys * @returns {Promise} */ export const getAPIKeys = async (): Promise => { try { const content = await fs.promises.readFile(getAPIKeyPath(), 'utf8') return JSON.parse(content) } catch (error) { const keyName = 'DefaultKey' const apiKey = generateAPIKey() const apiSecret = generateSecretHash(apiKey) const content = [ { keyName, apiKey, apiSecret, createdAt: moment().format('DD-MMM-YY'), id: randomBytes(16).toString('hex') } ] await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') return content } } /** * Add new API key * @param {string} keyName * @returns {Promise} */ export const addAPIKey = async (keyName: string): Promise => { const existingAPIKeys = await getAPIKeys() const apiKey = generateAPIKey() const apiSecret = generateSecretHash(apiKey) const content = [ ...existingAPIKeys, { keyName, apiKey, apiSecret, createdAt: moment().format('DD-MMM-YY'), id: randomBytes(16).toString('hex') } ] await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') return content } /** * Update existing API key * @param {string} keyIdToUpdate * @param {string} newKeyName * @returns {Promise} */ export const updateAPIKey = async (keyIdToUpdate: string, newKeyName: string): Promise => { const existingAPIKeys = await getAPIKeys() const keyIndex = existingAPIKeys.findIndex((key) => key.id === keyIdToUpdate) if (keyIndex < 0) return [] existingAPIKeys[keyIndex].keyName = newKeyName await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(existingAPIKeys), 'utf8') return existingAPIKeys } /** * Delete API key * @param {string} keyIdToDelete * @returns {Promise} */ export const deleteAPIKey = async (keyIdToDelete: string): Promise => { const existingAPIKeys = await getAPIKeys() const result = existingAPIKeys.filter((key) => key.id !== keyIdToDelete) await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(result), 'utf8') return result } /** * Encrypt credential data * @param {ICredentialDataDecrypted} data * @param {string} encryptionKey * @returns {string} */ export const encryptCredentialData = (data: ICredentialDataDecrypted, encryptionKey: string): string => { return AES.encrypt(JSON.stringify(data), encryptionKey).toString() } /** * Decrypt credential data * @param {string} data * @param {string} encryptionKey * @returns {ICredentialDataDecrypted} */ export const decryptCredentialData = (data: string, encryptionKey: string): ICredentialDataDecrypted => { const decryptedData = AES.decrypt(data, encryptionKey) try { return JSON.parse(decryptedData.toString(enc.Utf8)) } catch (e) { console.error(e) throw new Error('Credentials could not be decrypted.') } } /** * Transform ICredentialBody from req to Credential entity * @param {ICredentialBody} data * @returns {Credential} */ export const transformToCredentialEntity = async (body: ICredentialBody): Promise => { const encryptKey = await getEncryptionKey() const credentialBody = { name: body.name, nodeCredentialName: body.nodeCredentialName, credentialData: encryptCredentialData(body.credentialData, encryptKey) } const newCredential = new Credential() Object.assign(newCredential, credentialBody) return newCredential } /** * Returns the path of oauth2 html * @returns {string} */ export const getOAuth2HTMLPath = (): string => { return path.join(__dirname, '..', '..', 'oauth2.html') } /** * Construct directed graph and node dependencies score * @param {IReactFlowNode[]} reactFlowNodes * @param {IReactFlowEdge[]} reactFlowEdges */ export const constructGraphs = (reactFlowNodes: IReactFlowNode[], reactFlowEdges: IReactFlowEdge[]) => { const nodeDependencies = {} as INodeDependencies const graph = {} as INodeDirectedGraph for (let i = 0; i < reactFlowNodes.length; i += 1) { const nodeId = reactFlowNodes[i].id nodeDependencies[nodeId] = 0 graph[nodeId] = [] } for (let i = 0; i < reactFlowEdges.length; i += 1) { const source = reactFlowEdges[i].source const target = reactFlowEdges[i].target if (Object.prototype.hasOwnProperty.call(graph, source)) { graph[source].push(target) } else { graph[source] = [target] } nodeDependencies[target] += 1 } return { graph, nodeDependencies } } /** * Get starting node and check if flow is valid * @param {INodeDependencies} nodeDependencies * @param {IReactFlowNode[]} reactFlowNodes */ export const getStartingNode = (nodeDependencies: INodeDependencies, reactFlowNodes: IReactFlowNode[]) => { // Find starting node const startingNodeIds = [] as string[] Object.keys(nodeDependencies).forEach((nodeId) => { if (nodeDependencies[nodeId] === 0) { startingNodeIds.push(nodeId) } }) // Action nodes with 0 dependencies are not valid, must connected to source const faultyNodeLabels = [] for (let i = 0; i < startingNodeIds.length; i += 1) { const node = reactFlowNodes.find((nd) => nd.id === startingNodeIds[i]) if (node && node.data && node.data.type && node.data.type !== 'trigger' && node.data.type !== 'webhook') { faultyNodeLabels.push(node.data.label) } } return { faultyNodeLabels, startingNodeIds } } /** * Function to get both graphs and starting nodes * @param {Response} res * @param {IReactFlowNode[]} reactFlowNodes * @param {IReactFlowEdge[]} reactFlowEdges */ export const constructGraphsAndGetStartingNodes = (res: Response, reactFlowNodes: IReactFlowNode[], reactFlowEdges: IReactFlowEdge[]) => { const { graph, nodeDependencies } = constructGraphs(reactFlowNodes, reactFlowEdges) const { faultyNodeLabels, startingNodeIds } = getStartingNode(nodeDependencies, reactFlowNodes) if (faultyNodeLabels.length) { let message = `Action nodes must connected to source. Faulty nodes: ` for (let i = 0; i < faultyNodeLabels.length; i += 1) { message += `${faultyNodeLabels[i]}` if (i !== faultyNodeLabels.length - 1) { message += ', ' } } res.status(500).send(message) return } return { graph, startingNodeIds } } /** * Get variable value from outputResponses.output * @param {string} paramValue * @param {IReactFlowNode[]} reactFlowNodes * @param {string} key * @param {number} loopIndex * @returns {string} */ export const getVariableValue = (paramValue: string, reactFlowNodes: IReactFlowNode[], key: string, loopIndex: number): string => { let returnVal = paramValue const variableStack = [] const variableDict = {} as IVariableDict let startIdx = 0 const endIdx = returnVal.length - 1 while (startIdx < endIdx) { const substr = returnVal.substring(startIdx, startIdx + 2) // Store the opening double curly bracket if (substr === '{{') { variableStack.push({ substr, startIdx: startIdx + 2 }) } // Found the complete variable if (substr === '}}' && variableStack.length > 0 && variableStack[variableStack.length - 1].substr === '{{') { const variableStartIdx = variableStack[variableStack.length - 1].startIdx const variableEndIdx = startIdx const variableFullPath = returnVal.substring(variableStartIdx, variableEndIdx) // Split by first occurence of '[' to get just nodeId const [variableNodeId, ...rest] = variableFullPath.split('[') let variablePath = 'outputResponses.output' + '[' + rest.join('[') if (variablePath.includes('$index')) { variablePath = variablePath.split('$index').join(loopIndex.toString()) } const executedNode = reactFlowNodes.find((nd) => nd.id === variableNodeId) if (executedNode) { const resolvedVariablePath = getVariableValue(variablePath, reactFlowNodes, key, loopIndex) const variableValue = lodash.get(executedNode.data, resolvedVariablePath) variableDict[`{{${variableFullPath}}}`] = variableValue // For instance: const var1 = "some var" if (key === 'code' && typeof variableValue === 'string') variableDict[`{{${variableFullPath}}}`] = `"${variableValue}"` if (key === 'code' && typeof variableValue === 'object') variableDict[`{{${variableFullPath}}}`] = `${JSON.stringify(variableValue)}` } variableStack.pop() } startIdx += 1 } const variablePaths = Object.keys(variableDict) variablePaths.sort() // Sort by length of variable path because longer path could possibly contains nested variable variablePaths.forEach((path) => { const variableValue = variableDict[path] // Replace all occurence returnVal = returnVal.split(path).join(variableValue) }) return returnVal } /** * Get minimum variable array length from outputResponses.output * @param {string} paramValue * @param {IReactFlowNode[]} reactFlowNodes * @returns {number} */ export const getVariableLength = (paramValue: string, reactFlowNodes: IReactFlowNode[]): number => { let minLoop = Infinity const variableStack = [] let startIdx = 0 const endIdx = paramValue.length - 1 while (startIdx < endIdx) { const substr = paramValue.substring(startIdx, startIdx + 2) // Store the opening double curly bracket if (substr === '{{') { variableStack.push({ substr, startIdx: startIdx + 2 }) } // Found the complete variable if (substr === '}}' && variableStack.length > 0 && variableStack[variableStack.length - 1].substr === '{{') { const variableStartIdx = variableStack[variableStack.length - 1].startIdx const variableEndIdx = startIdx const variableFullPath = paramValue.substring(variableStartIdx, variableEndIdx) if (variableFullPath.includes('$index')) { // Split by first occurence of '[' to get just nodeId const [variableNodeId, ...rest] = variableFullPath.split('[') const variablePath = 'outputResponses.output' + '[' + rest.join('[') const [variableArrayPath, ..._] = variablePath.split('[$index]') const executedNode = reactFlowNodes.find((nd) => nd.id === variableNodeId) if (executedNode) { const variableValue = lodash.get(executedNode.data, variableArrayPath) if (Array.isArray(variableValue)) minLoop = Math.min(minLoop, variableValue.length) } } variableStack.pop() } startIdx += 1 } return minLoop } /** * Loop through each inputs and resolve variable if neccessary * @param {INodeData} reactFlowNodeData * @param {IReactFlowNode[]} reactFlowNodes * @returns {INodeData} */ export const resolveVariables = (reactFlowNodeData: INodeData, reactFlowNodes: IReactFlowNode[]): INodeData[] => { const flowNodeDataArray: INodeData[] = [] const flowNodeData = lodash.cloneDeep(reactFlowNodeData) const types = ['actions', 'networks', 'inputParameters'] const getMinForLoop = (paramsObj: ICommonObject) => { let minLoop = Infinity for (const key in paramsObj) { const paramValue = paramsObj[key] if (typeof paramValue === 'string' && paramValue.includes('$index')) { // node.data[$index].smtg minLoop = Math.min(minLoop, getVariableLength(paramValue, reactFlowNodes)) } if (Array.isArray(paramValue)) { for (let j = 0; j < paramValue.length; j += 1) { minLoop = Math.min(minLoop, getMinForLoop(paramValue[j] as ICommonObject)) } } } return minLoop } const getParamValues = (paramsObj: ICommonObject, loopIndex: number) => { for (const key in paramsObj) { const paramValue = paramsObj[key] if (typeof paramValue === 'string') { const resolvedValue = getVariableValue(paramValue, reactFlowNodes, key, loopIndex) paramsObj[key] = resolvedValue } if (typeof paramValue === 'number') { const paramValueStr = paramValue.toString() const resolvedValue = getVariableValue(paramValueStr, reactFlowNodes, key, loopIndex) paramsObj[key] = resolvedValue } if (Array.isArray(paramValue)) { for (let j = 0; j < paramValue.length; j += 1) { getParamValues(paramValue[j] as ICommonObject, loopIndex) } } } } let minLoop = Infinity for (let i = 0; i < types.length; i += 1) { const paramsObj = (flowNodeData as any)[types[i]] minLoop = Math.min(minLoop, getMinForLoop(paramsObj)) } if (minLoop === Infinity) { for (let i = 0; i < types.length; i += 1) { const paramsObj = (flowNodeData as any)[types[i]] getParamValues(paramsObj, -1) } return [flowNodeData] } else { for (let j = 0; j < minLoop; j += 1) { const clonedFlowNodeData = lodash.cloneDeep(flowNodeData) for (let i = 0; i < types.length; i += 1) { const paramsObj = (clonedFlowNodeData as any)[types[i]] getParamValues(paramsObj, j) } flowNodeDataArray.push(clonedFlowNodeData) } return flowNodeDataArray } } /** * Decrypt encrypted credentials with encryption key * @param {INodeData} nodeData */ export const decryptCredentials = async (nodeData: INodeData, appDataSource?: DataSource) => { if (!appDataSource) appDataSource = getDataSource() if (nodeData.credentials && nodeData.credentials.registeredCredential) { // @ts-ignore const credentialId: string = nodeData.credentials.registeredCredential?._id const credential = await appDataSource.getMongoRepository(Credential).findOneBy({ _id: new ObjectId(credentialId) }) if (!credential) return const encryptKey = await getEncryptionKey() const decryptedCredentialData = decryptCredentialData(credential.credentialData, encryptKey) nodeData.credentials = { ...nodeData.credentials, ...decryptedCredentialData } } await decryptWalletCredentials(nodeData) } /** * Decrypt encrypted wallet credentials with encryption key * @param {INodeData} nodeData */ export const decryptWalletCredentials = async (nodeData: INodeData) => { try { const filters = ['actions', 'credentials', 'networks', 'inputParameters'] for (const key in nodeData) { if (filters.includes(key)) { // Iterate object to find wallet for (const paramName in (nodeData as any)[key]) { if (paramName === 'wallet') { const walletString = (nodeData as any)[key][paramName] const walletDetails: IWallet = JSON.parse(walletString) const walletEncryptedString = walletDetails.walletCredential const encryptKey = await getEncryptionKey() // Decrpyt credentialData const decryptedCredentialData = decryptCredentialData(walletEncryptedString, encryptKey) walletDetails.walletCredential = JSON.stringify(decryptedCredentialData) ;(nodeData as any)[key][paramName] = JSON.stringify(walletDetails) } } } } } catch (e) { return } } /** * Process webhook * @param {Response} res * @param {Request} req * @param {DataSource} AppDataSource * @param {string} webhookEndpoint * @param {WebhookMethod} httpMethod * @param {IComponentNodesPool} componentNodes * @param {any} io */ export const processWebhook = async ( res: Response, req: Request, AppDataSource: DataSource, webhookEndpoint: string, httpMethod: WebhookMethod, componentNodes: IComponentNodesPool, io: any, deployedWorkflowsPool: DeployedWorkflowPool, activeTestWebhooksPool: ActiveTestWebhookPool ) => { try { // Find if webhook is in activeTestWebhookPool const testWebhookKey = `${webhookEndpoint}_${httpMethod}` if (Object.prototype.hasOwnProperty.call(activeTestWebhooksPool.activeTestWebhooks, testWebhookKey)) { const { nodes, edges, nodeData, clientId, isTestWorkflow, webhookNodeId } = activeTestWebhooksPool.activeTestWebhooks[testWebhookKey] const webhookNodeInstance = componentNodes[nodeData.name] as IWebhookNode await decryptCredentials(nodeData) if (!isTestWorkflow) { nodeData.req = req const result = await webhookNodeInstance.runWebhook!.call(webhookNodeInstance, nodeData) if (result === null) return res.status(200).send('OK!') // Emit webhook result io.to(clientId).emit('testWebhookNodeResponse', result) // Delete webhook from 3rd party apps and from pool activeTestWebhooksPool.remove(testWebhookKey, componentNodes) const webhookResponseCode = (nodeData.inputParameters?.responseCode as number) || 200 if ( (nodeData.inputParameters?.returnType as string) === 'lastNodeResponse' || nodeData.name === 'chainLinkFunctionWebhook' ) { const webhookResponseData = result || [] return res.status(webhookResponseCode).json(webhookResponseData) } else { const webhookResponseData = (nodeData.inputParameters?.responseData as string) || `Webhook ${req.originalUrl} received!` return res.status(webhookResponseCode).send(webhookResponseData) } } else { nodeData.req = req const result = await webhookNodeInstance.runWebhook!.call(webhookNodeInstance, nodeData) if (result === null) return res.status(200).send('OK!') const newWorkflowExecutedData = { nodeId: webhookNodeId, nodeLabel: nodeData.label, data: result, status: 'FINISHED' } as IWorkflowExecutedData io.to(clientId).emit('testWorkflowNodeResponse', newWorkflowExecutedData) // Delete webhook from 3rd party apps and from pool activeTestWebhooksPool.remove(testWebhookKey, componentNodes) const { graph } = constructGraphs(nodes, edges) const webhookResponseCode = (nodeData.inputParameters?.responseCode as number) || 200 if ( (nodeData.inputParameters?.returnType as string) === 'lastNodeResponse' || nodeData.name === 'chainLinkFunctionWebhook' ) { const lastExecutedResult = await testWorkflow( webhookNodeId, result.length ? [{ data: result[0].data }] : [], nodes, edges, graph, componentNodes, clientId, io, true ) const webhookResponseData = lastExecutedResult || [] return res.status(webhookResponseCode).json(webhookResponseData) } else { await testWorkflow( webhookNodeId, result.length ? [{ data: result[0].data }] : [], nodes, edges, graph, componentNodes, clientId, io ) const webhookResponseData = (nodeData.inputParameters?.responseData as string) || `Webhook ${req.originalUrl} received!` return res.status(webhookResponseCode).send(webhookResponseData) } } } else { const webhook = await AppDataSource.getMongoRepository(Webhook).findOneBy({ webhookEndpoint, httpMethod }) if (!webhook) { res.status(404).send(`Webhook ${req.originalUrl} not found`) return } const nodeId = webhook.nodeId const workflowShortId = webhook.workflowShortId const workflow = await AppDataSource.getMongoRepository(Workflow).findOneBy({ shortId: workflowShortId }) if (!workflow) { res.status(404).send(`Workflow ${workflowShortId} not found`) return } const flowDataString = workflow.flowData const flowData: IReactFlowObject = JSON.parse(flowDataString) const reactFlowNodes = flowData.nodes as IReactFlowNode[] const reactFlowEdges = flowData.edges as IReactFlowEdge[] const reactFlowNode = reactFlowNodes.find((nd) => nd.id === nodeId) if (!reactFlowNode) { res.status(404).send(`Node ${nodeId} not found`) return } const nodeData = reactFlowNode.data const nodeName = nodeData.name // Start workflow const { graph, nodeDependencies } = constructGraphs(reactFlowNodes, reactFlowEdges) const { faultyNodeLabels, startingNodeIds } = getStartingNode(nodeDependencies, reactFlowNodes) if (faultyNodeLabels.length) { let message = `Action nodes must connected to source. Faulty nodes: ` for (let i = 0; i < faultyNodeLabels.length; i += 1) { message += `${faultyNodeLabels[i]}` if (i !== faultyNodeLabels.length - 1) { message += ', ' } } res.status(500).send(message) return } const nodeInstance = componentNodes[nodeName] const webhookNode = nodeInstance as IWebhookNode nodeData.req = req const result = (await webhookNode.runWebhook!.call(webhookNode, nodeData)) || [] if (result === null) return res.status(200).send('OK!') const webhookResponseCode = (nodeData.inputParameters?.responseCode as number) || 200 /** * Very specific use case for chainLinkFunctionWebhook * This is to prevent workflow from triggering multiple times because * Each oracle node runs the same computation in the Off-chain Reporting protocol, hence webhook will be called multiple times * By storing sessionId, we can keep track if this is the same computation run from oracle node * Info: https://docs.chain.link/chainlink-functions/tutorials/api-post-data */ let chainLinkSessionId = '' const updateWebhookData = async (chainLinkSessionId: string, data?: any) => { const content: ICommonObject = { sessionId: chainLinkSessionId } if (data) content.data = data const body = { webhookId: JSON.stringify(content) } const updateWebhook = new Webhook() Object.assign(updateWebhook, body) AppDataSource.getMongoRepository(Webhook).merge(webhook, updateWebhook) await AppDataSource.getMongoRepository(Webhook).save(webhook) } if ( nodeData.name === 'chainLinkFunctionWebhook' && result.length && result[0].data.headers && ((result[0].data.headers as any)['cf-session-id'] || (result[0].data.headers as any)['CF-SESSION-ID']) ) { const sessionId = (result[0].data.headers as any)['cf-session-id'] if (!webhook.webhookId) { chainLinkSessionId = sessionId await updateWebhookData(chainLinkSessionId) } else { const lastSavedSessionId = JSON.parse(webhook.webhookId)?.sessionId if (lastSavedSessionId !== sessionId) { chainLinkSessionId = sessionId await updateWebhookData(chainLinkSessionId) } else { const promise = () => { return new Promise((resolve, reject) => { let count = 10 const timeout = setInterval(async () => { if (count < 0) { clearInterval(timeout) reject(new Error(`Chainlink Function Webhook Timeout`)) } const webhook = await AppDataSource.getMongoRepository(Webhook).findOneBy({ webhookEndpoint, httpMethod }) if (!webhook) { clearInterval(timeout) reject(new Error(`Error finding Chainlink Function Webhook`)) } else { const lastSavedData = JSON.parse(webhook.webhookId)?.data if (lastSavedData) { clearInterval(timeout) resolve(lastSavedData) } } count -= 1 }, 1000) }) } const responseData = await promise() return res.status(webhookResponseCode).json(responseData) } } } const workflowExecutedData = (await deployedWorkflowsPool.startWorkflow( workflowShortId, reactFlowNode, reactFlowNode.id, result, componentNodes, startingNodeIds, graph )) as unknown as IWorkflowExecutedData[] if ((nodeData.inputParameters?.returnType as string) === 'lastNodeResponse' || nodeData.name === 'chainLinkFunctionWebhook') { const lastExecutedResult = workflowExecutedData[workflowExecutedData.length - 1] const webhookResponseData = lastExecutedResult?.data || [] if (chainLinkSessionId) await updateWebhookData(chainLinkSessionId, webhookResponseData) return res.status(webhookResponseCode).json(webhookResponseData) } else { const webhookResponseData = (nodeData.inputParameters?.responseData as string) || `Webhook ${req.originalUrl} received!` return res.status(webhookResponseCode).send(webhookResponseData) } } } catch (error) { res.status(500).send(`Webhook error: ${error}`) return } } /** * Update credential in DB after oAuth2 tokens have been refreshed * @param {INodeExecutionData[] | null} result * @param {INodeData} nodeData * @param {DataSource} appDataSource */ const updateCredentialAfterOAuth2TokenRefreshed = async ( result: INodeExecutionData[] | null, nodeData: INodeData, appDataSource?: DataSource ) => { if (!result || !result.length) return if (!appDataSource) appDataSource = getDataSource() let access_token = '' let expires_in = '' for (let i = 0; i < result.length; i += 1) { if (Object.prototype.hasOwnProperty.call(result[i], OAUTH2_REFRESHED)) { const refreshData = result[i][OAUTH2_REFRESHED] as unknown as IOAuth2RefreshResponse access_token = refreshData.access_token expires_in = refreshData.expires_in break } } result.forEach((el) => { if (el[OAUTH2_REFRESHED]) delete el[OAUTH2_REFRESHED] return el }) // Update credential if (access_token && expires_in && nodeData.credentials && nodeData.credentials.registeredCredential) { // @ts-ignore const credentialId = nodeData.credentials.registeredCredential._id as string const credential = await appDataSource.getMongoRepository(Credential).findOneBy({ _id: new ObjectId(credentialId) }) if (!credential) return const encryptKey = await getEncryptionKey() const decryptedCredentialData = decryptCredentialData(credential.credentialData, encryptKey) const body: ICredentialBody = { name: credential.name, nodeCredentialName: credential.nodeCredentialName, credentialData: { ...decryptedCredentialData, access_token, expires_in } } const updateCredential = await transformToCredentialEntity(body) appDataSource.getMongoRepository(Credential).merge(credential, updateCredential) await appDataSource.getMongoRepository(Credential).save(credential) } } /** * Check if oAuth2 token refreshed * @param {INodeExecutionData[] | null} result * @param {INodeData} nodeData * @param {DataSource} appDataSource */ export const checkOAuth2TokenRefreshed = (result: INodeExecutionData[] | null, nodeData: INodeData, appDataSource?: DataSource) => { const credentialMethod = nodeData.credentials?.credentialMethod as string if (credentialMethod && credentialMethod.toLowerCase().includes('oauth2')) { updateCredentialAfterOAuth2TokenRefreshed(result, nodeData, appDataSource) } } /** * Update reactFlowNodes so that resolveVariables is called, it is getting updated result * @param {IReactFlowNode[]} reactFlowNodes * @param {number} nodeIndex * @param {INodeExecutionData[]} newResult */ export const updateNodeOutput = (reactFlowNodes: IReactFlowNode[], nodeIndex: number, newResult: INodeExecutionData[] = []) => { if (reactFlowNodes[nodeIndex].data.outputResponses) { reactFlowNodes[nodeIndex].data.outputResponses = { ...reactFlowNodes[nodeIndex].data.outputResponses, output: newResult } } else { reactFlowNodes[nodeIndex].data.outputResponses = { submit: true, needRetest: null, output: newResult } } } /** * Test Workflow from starting node to end * @param {string} startingNodeId * @param {INodeExecutionData[]} startingNodeExecutedData * @param {IReactFlowNode[]} reactFlowNodes * @param {IReactFlowEdge[]} reactFlowEdges * @param {INodeDirectedGraph} graph * @param {IComponentNodesPool} componentNodes * @param {string} clientId * @param {any} io */ export const testWorkflow = async ( startingNodeId: string, startingNodeExecutedData: INodeExecutionData[], reactFlowNodes: IReactFlowNode[], reactFlowEdges: IReactFlowEdge[], graph: INodeDirectedGraph, componentNodes: IComponentNodesPool, clientId: string, io: any, returnLastExecutedResult?: boolean ) => { // Create a Queue and add our initial node in it const startingNodeIds = [startingNodeId] const startingNodeIndex = reactFlowNodes.findIndex((nd) => nd.id === startingNodeId) updateNodeOutput(reactFlowNodes, startingNodeIndex, startingNodeExecutedData) const nodeQueue = [] as INodeQueue[] const exploredNode = {} as IExploredNode // In the case of infinite loop, only max 3 loops will be executed const maxLoop = 3 // Keep track of last executed result let lastExecutedResult: any for (let i = 0; i < startingNodeIds.length; i += 1) { nodeQueue.push({ nodeId: startingNodeIds[i], depth: 0 }) exploredNode[startingNodeIds[i]] = { remainingLoop: maxLoop, lastSeenDepth: 0 } } while (nodeQueue.length) { const { nodeId, depth } = nodeQueue.shift() as INodeQueue const ignoreNodeIds: string[] = [] if (!startingNodeIds.includes(nodeId)) { const reactFlowNode = reactFlowNodes.find((nd) => nd.id === nodeId) const nodeIndex = reactFlowNodes.findIndex((nd) => nd.id === nodeId) if (!reactFlowNode || reactFlowNode === undefined || nodeIndex < 0) continue try { const nodeInstanceFilePath = componentNodes[reactFlowNode.data.name].filePath const nodeModule = await import(nodeInstanceFilePath) const newNodeInstance = new nodeModule.nodeClass() await decryptCredentials(reactFlowNode.data) const reactFlowNodeData: INodeData[] = resolveVariables(reactFlowNode.data, reactFlowNodes) let results: INodeExecutionData[] = [] for (let i = 0; i < reactFlowNodeData.length; i += 1) { const result = await newNodeInstance.run!.call(newNodeInstance, reactFlowNodeData[i]) checkOAuth2TokenRefreshed(result, reactFlowNodeData[i]) if (result) results.push(...result) } updateNodeOutput(reactFlowNodes, nodeIndex, results) // Determine which nodes to route next when it comes to ifElse if (results.length && nodeId.includes('ifElse')) { let anchorIndex = -1 if (Array.isArray(results) && Object.keys((results as any)[0].data).length === 0) { anchorIndex = 0 } else if (Array.isArray(results) && Object.keys((results as any)[1].data).length === 0) { anchorIndex = 1 } const ifElseEdge = reactFlowEdges.find( (edg) => edg.source === nodeId && edg.sourceHandle === `${nodeId}-output-${anchorIndex}` ) if (ifElseEdge) { ignoreNodeIds.push(ifElseEdge.target) } } const newWorkflowExecutedData = { nodeId, nodeLabel: reactFlowNode.data.label, data: results, status: 'FINISHED' } as IWorkflowExecutedData lastExecutedResult = results io.to(clientId).emit('testWorkflowNodeResponse', newWorkflowExecutedData) } catch (e: any) { console.error(e) const newWorkflowExecutedData = { nodeId, nodeLabel: reactFlowNode.data.label, data: [{ error: e.message }], status: 'ERROR' } as IWorkflowExecutedData lastExecutedResult = [{ error: e.message }] io.to(clientId).emit('testWorkflowNodeResponse', newWorkflowExecutedData) return } } const neighbourNodeIds = graph[nodeId] const nextDepth = depth + 1 for (let i = 0; i < neighbourNodeIds.length; i += 1) { const neighNodeId = neighbourNodeIds[i] if (!ignoreNodeIds.includes(neighNodeId)) { // If nodeId has been seen, cycle detected if (Object.prototype.hasOwnProperty.call(exploredNode, neighNodeId)) { const { remainingLoop, lastSeenDepth } = exploredNode[neighNodeId] if (lastSeenDepth === nextDepth) continue if (remainingLoop === 0) { break } const remainingLoopMinusOne = remainingLoop - 1 exploredNode[neighNodeId] = { remainingLoop: remainingLoopMinusOne, lastSeenDepth: nextDepth } nodeQueue.push({ nodeId: neighNodeId, depth: nextDepth }) } else { exploredNode[neighNodeId] = { remainingLoop: maxLoop, lastSeenDepth: nextDepth } nodeQueue.push({ nodeId: neighNodeId, depth: nextDepth }) } } } } io.to(clientId).emit('testWorkflowNodeFinish') if (returnLastExecutedResult) return lastExecutedResult } ================================================ FILE: packages/server/tsconfig.json ================================================ { "compilerOptions": { "lib": ["es2017"], "target": "es2017" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, "experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */, "emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */, "module": "commonjs" /* Specify what module code is generated. */, "outDir": "dist", "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, "strict": true /* Enable all strict type-checking options. */, "skipLibCheck": true /* Skip type checking all .d.ts files. */, "sourceMap": true, "strictPropertyInitialization": false, "declaration": true }, "include": ["src"] } ================================================ FILE: packages/ui/.npmignore ================================================ /tests /src /public !build yarn-debug.log* yarn-error.log* .eslintrc .prettierignore .prettierrc jsconfig.json ================================================ FILE: packages/ui/README.md ================================================ # Outerbridge UI React frontend ui for Outerbridge. ![Outerbridge](https://raw.githubusercontent.com/Outerbridgeio/Outerbridge/master/assets/outerbridge_brand.png) Install: ```bash npm i outerbridge-ui ``` ## License Source code in this repository is made available under the [Apache License Version 2.0](https://github.com/Outerbridgeio/Outerbridge/blob/master/LICENSE.md). ================================================ FILE: packages/ui/jsconfig.json ================================================ { "compilerOptions": { "target": "esnext", "module": "commonjs", "baseUrl": "src" }, "include": ["src/**/*"], "exclude": ["node_modules"] } ================================================ FILE: packages/ui/package.json ================================================ { "name": "outerbridge-ui", "version": "1.0.15", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://outerbridge.io", "author": { "name": "Henry Heng", "email": "henryheng@outerbridge.io" }, "dependencies": { "@emotion/cache": "^11.4.0", "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", "@metamask/jazzicon": "^2.0.0", "@mui/icons-material": "^5.0.3", "@mui/material": "^5.11.12", "@tabler/icons": "^1.39.1", "clsx": "^1.1.1", "ethers": "^5.6.8", "formik": "^2.2.6", "framer-motion": "^4.1.13", "history": "^5.0.0", "html-react-parser": "^3.0.4", "lodash": "^4.17.21", "moment": "^2.29.3", "notistack": "^2.0.4", "prismjs": "^1.28.0", "prop-types": "^15.7.2", "react": "^18.2.0", "react-datepicker": "^4.8.0", "react-device-detect": "^1.17.0", "react-dom": "^18.2.0", "react-jazzicon": "^1.0.4", "react-json-view": "^1.21.3", "react-perfect-scrollbar": "^1.5.8", "react-redux": "^8.0.5", "react-router": "~6.3.0", "react-router-dom": "~6.3.0", "react-select": "^5.3.2", "react-simple-code-editor": "^0.11.2", "reactflow": "^11.5.6", "redux": "^4.0.5", "socket.io-client": "^4.5.1", "yup": "^0.32.9" }, "scripts": { "start": "react-scripts start", "dev": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "babel": { "presets": [ "@babel/preset-react" ] }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "devDependencies": { "@babel/eslint-parser": "^7.15.8", "@testing-library/jest-dom": "^5.11.10", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^12.8.3", "pretty-quick": "^3.1.3", "react-scripts": "^5.0.1", "sass": "^1.42.1", "typescript": "^4.8.4" } } ================================================ FILE: packages/ui/public/index.html ================================================ Outerbridge - Automate Web3 and Web2 applications
    ================================================ FILE: packages/ui/src/App.js ================================================ import { useSelector } from 'react-redux' import { ThemeProvider } from '@mui/material/styles' import { CssBaseline, StyledEngineProvider } from '@mui/material' // routing import Routes from 'routes' // defaultTheme import themes from 'themes' // project imports import NavigationScroll from 'layout/NavigationScroll' // ==============================|| APP ||============================== // const App = () => { const customization = useSelector((state) => state.customization) return ( ) } export default App ================================================ FILE: packages/ui/src/api/apikey.js ================================================ import client from './client' const getAllAPIKeys = () => client.get('/apikey') const createNewAPI = (body) => client.post(`/apikey`, body) const updateAPI = (id, body) => client.put(`/apikey/${id}`, body) const deleteAPI = (id) => client.delete(`/apikey/${id}`) export default { getAllAPIKeys, createNewAPI, updateAPI, deleteAPI } ================================================ FILE: packages/ui/src/api/client.js ================================================ import axios from 'axios' import { baseURL } from 'store/constant' const apiClient = axios.create({ baseURL: `${baseURL}/api/v1`, headers: { 'Content-type': 'application/json' } }) export default apiClient ================================================ FILE: packages/ui/src/api/contracts.js ================================================ import client from './client' const getAllContracts = () => client.get('/contracts') const getSpecificContract = (id) => client.get(`/contracts/${id}`) const createNewContract = (body) => client.post(`/contracts`, body) // body: IContract const updateContract = (id, body) => client.put(`/contracts/${id}`, body) // body: IContract const getContractABI = (body) => client.post(`/contracts/getabi`, body) //body: IContractRequestBody const deleteContract = (id) => client.delete(`/contracts/${id}`) export default { getAllContracts, getSpecificContract, createNewContract, updateContract, getContractABI, deleteContract } ================================================ FILE: packages/ui/src/api/credential.js ================================================ import client from './client' const getCredentials = (nodeCredentialName) => client.get('/credentials', { params: { nodeCredentialName } }) const getCredentialParams = (name) => client.get(`/node-credentials/${name}`) const getSpecificCredential = (id, isEncrypted) => client.get(`/credentials/${id}`, { params: { isEncrypted } }) const createNewCredential = (credentialBody) => client.post(`/credentials`, credentialBody) //credentialBody: ICredential const updateCredential = (id, credentialBody) => client.put(`/credentials/${id}`, credentialBody) //credentialBody: ICredential const deleteCredential = (id) => client.delete(`/credentials/${id}`) export default { getCredentials, getCredentialParams, getSpecificCredential, createNewCredential, updateCredential, deleteCredential } ================================================ FILE: packages/ui/src/api/executions.js ================================================ import client from './client' const getAllExecutions = () => client.get('/executions') const getSpecificExecution = (shortId) => client.get(`/executions/${shortId}`) const createNewExecution = (body) => client.post(`/executions`, body) //body: IExecution const updateExecution = (shortId, body) => client.put(`/executions/${shortId}`, body) //body: IExecution const deleteExecution = (shortId) => client.delete(`/executions/${shortId}`) export default { getAllExecutions, getSpecificExecution, createNewExecution, updateExecution, deleteExecution } ================================================ FILE: packages/ui/src/api/nodes.js ================================================ import client from './client' const getAllNodes = () => client.get('/nodes') const getSpecificNode = (name) => client.get(`/nodes/${name}`) const testNode = (name, body) => client.post(`/node-test/${name}`, body) //body: ITestNodeBody const loadMethodNode = (name, nodeData) => client.post(`/node-load-method/${name}`, nodeData) //nodeData: INodeData const removeTestTriggers = () => client.post(`/remove-test-triggers`) export default { getAllNodes, getSpecificNode, testNode, loadMethodNode, removeTestTriggers } ================================================ FILE: packages/ui/src/api/oauth2.js ================================================ import client from './client' const geOAuth2PopupURL = (credentialId) => client.get('/oauth2', { params: { credentialId } }) const geOAuth2RedirectURL = () => client.get('/oauth2-redirecturl') export default { geOAuth2PopupURL, geOAuth2RedirectURL } ================================================ FILE: packages/ui/src/api/wallets.js ================================================ import client from './client' const getAllWallets = () => client.get('/wallets') const getSpecificWallet = (id) => client.get(`/wallets/${id}`) const createNewWallet = (body) => client.post(`/wallets`, body) //body: IWallet const updateWallet = (id, body) => client.put(`/wallets/${id}`, body) //body: IWallet const deleteWallet = (id) => client.delete(`/wallets/${id}`) const getWalletCredential = (id) => client.get(`/wallets/credential/${id}`) export default { getAllWallets, getSpecificWallet, createNewWallet, updateWallet, deleteWallet, getWalletCredential } ================================================ FILE: packages/ui/src/api/webhooks.js ================================================ import client from './client' const deleteAllTestWebhooks = () => client.post(`/remove-test-webhooks`) const getTunnelURL = () => client.get(`/get-tunnel-url`) export default { getTunnelURL, deleteAllTestWebhooks } ================================================ FILE: packages/ui/src/api/workflows.js ================================================ import client from './client' const getAllWorkflows = () => client.get('/workflows') const getSpecificWorkflow = (shortId) => client.get(`/workflows/${shortId}`) const createNewWorkflow = (body) => client.post(`/workflows`, body) //body: IWorkflow const updateWorkflow = (shortId, body) => client.put(`/workflows/${shortId}`, body) //body: IWorkflow const deployWorkflow = (shortId, body) => client.post(`/workflows/deploy/${shortId}`, body || {}) //body: { halt: boolean } const testWorkflow = (startingNodeId, body) => client.post(`/workflows/test/${startingNodeId}`, body) //body: ITestWorkflowBody const deleteWorkflow = (shortId) => client.delete(`/workflows/${shortId}`) export default { getAllWorkflows, getSpecificWorkflow, createNewWorkflow, updateWorkflow, deployWorkflow, deleteWorkflow, testWorkflow } ================================================ FILE: packages/ui/src/assets/scss/_themes-vars.module.scss ================================================ // paper & background $paper: #ffffff; // primary $primaryLight: #e3f2fd; $primaryMain: #2196f3; $primaryDark: #1e88e5; $primary200: #90caf9; $primary800: #1565c0; // secondary $secondaryLight: #ede7f6; $secondaryMain: #673ab7; $secondaryDark: #5e35b1; $secondary200: #b39ddb; $secondary800: #4527a0; // success Colors $successLight: #cdf5d8; $success200: #69f0ae; $successMain: #00e676; $successDark: #00c853; // error $errorLight: #f3d2d2; $errorMain: #f44336; $errorDark: #c62828; // orange $orangeLight: #fbe9e7; $orangeMain: #ffab91; $orangeDark: #d84315; // warning $warningLight: #fff8e1; $warningMain: #ffe57f; $warningDark: #ffc107; // grey $grey50: #fafafa; $grey100: #f5f5f5; $grey200: #eeeeee; $grey300: #e0e0e0; $grey500: #9e9e9e; $grey600: #757575; $grey700: #616161; $grey900: #212121; // ==============================|| DARK THEME VARIANTS ||============================== // // paper & background $darkBackground: #191b1f; $darkPaper: #191b1f; // dark 800 & 900 $darkLevel1: #252525; // level 1 $darkLevel2: #242424; // level 2 // primary dark $darkPrimaryLight: #23262c; $darkPrimaryMain: #23262c; $darkPrimaryDark: #191b1f; $darkPrimary200: #c9d4e9; $darkPrimary800: #32353b; // secondary dark $darkSecondaryLight: #454c59; $darkSecondaryMain: #7c4dff; $darkSecondaryDark: #ffffff; $darkSecondary200: #32353b; $darkSecondary800: #6200ea; // text variants $darkTextTitle: #d7dcec; $darkTextPrimary: #bdc8f0; $darkTextSecondary: #8492c4; // ==============================|| JAVASCRIPT ||============================== // :export { // paper & background paper: $paper; // primary primaryLight: $primaryLight; primary200: $primary200; primaryMain: $primaryMain; primaryDark: $primaryDark; primary800: $primary800; // secondary secondaryLight: $secondaryLight; secondary200: $secondary200; secondaryMain: $secondaryMain; secondaryDark: $secondaryDark; secondary800: $secondary800; // success successLight: $successLight; success200: $success200; successMain: $successMain; successDark: $successDark; // error errorLight: $errorLight; errorMain: $errorMain; errorDark: $errorDark; // orange orangeLight: $orangeLight; orangeMain: $orangeMain; orangeDark: $orangeDark; // warning warningLight: $warningLight; warningMain: $warningMain; warningDark: $warningDark; // grey grey50: $grey50; grey100: $grey100; grey200: $grey200; grey300: $grey300; grey500: $grey500; grey600: $grey600; grey700: $grey700; grey900: $grey900; // ==============================|| DARK THEME VARIANTS ||============================== // // paper & background darkPaper: $darkPaper; darkBackground: $darkBackground; // dark 800 & 900 darkLevel1: $darkLevel1; darkLevel2: $darkLevel2; // text variants darkTextTitle: $darkTextTitle; darkTextPrimary: $darkTextPrimary; darkTextSecondary: $darkTextSecondary; // primary dark darkPrimaryLight: $darkPrimaryLight; darkPrimaryMain: $darkPrimaryMain; darkPrimaryDark: $darkPrimaryDark; darkPrimary200: $darkPrimary200; darkPrimary800: $darkPrimary800; // secondary dark darkSecondaryLight: $darkSecondaryLight; darkSecondaryMain: $darkSecondaryMain; darkSecondaryDark: $darkSecondaryDark; darkSecondary200: $darkSecondary200; darkSecondary800: $darkSecondary800; } ================================================ FILE: packages/ui/src/assets/scss/style.scss ================================================ // color variants @import 'themes-vars.module.scss'; // third-party @import '~react-perfect-scrollbar/dist/css/styles.css'; // ==============================|| LIGHT BOX ||============================== // .fullscreen .react-images__blanket { z-index: 1200; } // ==============================|| PERFECT SCROLLBAR ||============================== // .scrollbar-container { .ps__rail-y { &:hover > .ps__thumb-y, &:focus > .ps__thumb-y, &.ps--clicking .ps__thumb-y { background-color: $grey500; width: 5px; } } .ps__thumb-y { background-color: $grey500; border-radius: 6px; width: 5px; right: 0; } } .scrollbar-container.ps, .scrollbar-container > .ps { &.ps--active-y > .ps__rail-y { width: 5px; background-color: transparent !important; z-index: 999; &:hover, &.ps--clicking { width: 5px; background-color: transparent; } } &.ps--scrolling-y > .ps__rail-y, &.ps--scrolling-x > .ps__rail-x { opacity: 0.4; background-color: transparent; } } // ==============================|| ANIMATION KEYFRAMES ||============================== // @keyframes wings { 50% { transform: translateY(-40px); } 100% { transform: translateY(0px); } } @keyframes blink { 50% { opacity: 0; } 100% { opacity: 1; } } @keyframes bounce { 0%, 20%, 53%, to { animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); transform: translateZ(0); } 40%, 43% { animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); transform: translate3d(0, -5px, 0); } 70% { animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); transform: translate3d(0, -7px, 0); } 80% { transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); transform: translateZ(0); } 90% { transform: translate3d(0, -2px, 0); } } @keyframes slideY { 0%, 50%, 100% { transform: translateY(0px); } 25% { transform: translateY(-10px); } 75% { transform: translateY(10px); } } @keyframes slideX { 0%, 50%, 100% { transform: translateX(0px); } 25% { transform: translateX(-10px); } 75% { transform: translateX(10px); } } ================================================ FILE: packages/ui/src/config.js ================================================ const config = { // basename: only at build time to set, and Don't add '/' at end off BASENAME for breadcrumbs, also Don't put only '/' use blank('') instead, basename: '', defaultPath: '/workflows', fontFamily: `'Roboto', sans-serif`, borderRadius: 12 } export default config ================================================ FILE: packages/ui/src/hooks/useApi.js ================================================ import { useState } from 'react' export default (apiFunc) => { const [data, setData] = useState(null) const [error, setError] = useState(null) const [loading, setLoading] = useState(false) const request = async (...args) => { setLoading(true) try { const result = await apiFunc(...args) setData(result.data) } catch (err) { setError(err || 'Unexpected Error!') } finally { setLoading(false) } } return { data, error, loading, request } } ================================================ FILE: packages/ui/src/hooks/useConfirm.js ================================================ import { useContext } from 'react' import ConfirmContext from 'store/context/ConfirmContext' import { HIDE_CONFIRM, SHOW_CONFIRM } from 'store/actions' let resolveCallback const useConfirm = () => { const [confirmState, dispatch] = useContext(ConfirmContext) const closeConfirm = () => { dispatch({ type: HIDE_CONFIRM }) } const onConfirm = () => { closeConfirm() resolveCallback(true) } const onCancel = () => { closeConfirm() resolveCallback(false) } const confirm = (confirmPayload) => { dispatch({ type: SHOW_CONFIRM, payload: confirmPayload }) return new Promise((res) => { resolveCallback = res }) } return { confirm, onConfirm, onCancel, confirmState } } export default useConfirm ================================================ FILE: packages/ui/src/hooks/useScriptRef.js ================================================ import { useEffect, useRef } from 'react' // ==============================|| ELEMENT REFERENCE HOOKS ||============================== // const useScriptRef = () => { const scripted = useRef(true) useEffect( () => () => { scripted.current = false }, [] ) return scripted } export default useScriptRef ================================================ FILE: packages/ui/src/index.js ================================================ import React from 'react' import App from './App' import { store } from 'store' import { createRoot } from 'react-dom/client' // style + assets import 'assets/scss/style.scss' // third party import { BrowserRouter } from 'react-router-dom' import { Provider } from 'react-redux' import { SnackbarProvider } from 'notistack' import ConfirmContextProvider from 'store/context/ConfirmContextProvider' const container = document.getElementById('root') const root = createRoot(container) root.render( ) ================================================ FILE: packages/ui/src/layout/MainLayout/Header/index.js ================================================ import PropTypes from 'prop-types' import { useSelector, useDispatch } from 'react-redux' import { useState } from 'react' // material-ui import { useTheme } from '@mui/material/styles' import { Avatar, Box, ButtonBase, Switch } from '@mui/material' import { styled } from '@mui/material/styles' // project imports import LogoSection from '../LogoSection' // assets import { IconMenu2 } from '@tabler/icons' // store import { SET_DARKMODE } from 'store/actions' // ==============================|| MAIN NAVBAR / HEADER ||============================== // const MaterialUISwitch = styled(Switch)(({ theme }) => ({ width: 62, height: 34, padding: 7, '& .MuiSwitch-switchBase': { margin: 1, padding: 0, transform: 'translateX(6px)', '&.Mui-checked': { color: '#fff', transform: 'translateX(22px)', '& .MuiSwitch-thumb:before': { backgroundImage: `url('data:image/svg+xml;utf8,')` }, '& + .MuiSwitch-track': { opacity: 1, backgroundColor: theme.palette.mode === 'dark' ? '#8796A5' : '#aab4be' } } }, '& .MuiSwitch-thumb': { backgroundColor: theme.palette.mode === 'dark' ? '#003892' : '#001e3c', width: 32, height: 32, '&:before': { content: "''", position: 'absolute', width: '100%', height: '100%', left: 0, top: 0, backgroundRepeat: 'no-repeat', backgroundPosition: 'center', backgroundImage: `url('data:image/svg+xml;utf8,')` } }, '& .MuiSwitch-track': { opacity: 1, backgroundColor: theme.palette.mode === 'dark' ? '#8796A5' : '#aab4be', borderRadius: 20 / 2 } })) const Header = ({ handleLeftDrawerToggle }) => { const theme = useTheme() const customization = useSelector((state) => state.customization) const [isDark, setIsDark] = useState(customization.isDarkMode) const dispatch = useDispatch() const changeDarkMode = () => { dispatch({ type: SET_DARKMODE, isDarkMode: !isDark }) setIsDark((isDark) => !isDark) localStorage.setItem('isDarkMode', !isDark) } return ( <> {/* logo & toggler button */} ) } Header.propTypes = { handleLeftDrawerToggle: PropTypes.func } export default Header ================================================ FILE: packages/ui/src/layout/MainLayout/LogoSection/index.js ================================================ import { Link } from 'react-router-dom' // material-ui import { ButtonBase } from '@mui/material' // project imports import config from 'config' import Logo from 'ui-component/Logo' // ==============================|| MAIN LOGO ||============================== // const LogoSection = () => ( ) export default LogoSection ================================================ FILE: packages/ui/src/layout/MainLayout/Sidebar/MenuList/NavCollapse/index.js ================================================ import PropTypes from 'prop-types' import { useState } from 'react' import { useSelector } from 'react-redux' // material-ui import { useTheme } from '@mui/material/styles' import { Collapse, List, ListItemButton, ListItemIcon, ListItemText, Typography } from '@mui/material' // project imports import NavItem from '../NavItem' // assets import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord' import { IconChevronDown, IconChevronUp } from '@tabler/icons' // ==============================|| SIDEBAR MENU LIST COLLAPSE ITEMS ||============================== // const NavCollapse = ({ menu, level }) => { const theme = useTheme() const customization = useSelector((state) => state.customization) const [open, setOpen] = useState(false) const [selected, setSelected] = useState(null) const handleClick = () => { setOpen(!open) setSelected(!selected ? menu.id : null) } // menu collapse & item const menus = menu.children?.map((item) => { switch (item.type) { case 'collapse': return case 'item': return default: return ( Menu Items Error ) } }) const Icon = menu.icon const menuIcon = menu.icon ? ( ) : ( 0 ? 'inherit' : 'medium'} /> ) return ( <> 1 ? 'transparent !important' : 'inherit', py: level > 1 ? 1 : 1.25, pl: `${level * 24}px` }} selected={selected === menu.id} onClick={handleClick} > {menuIcon} {menu.title} } secondary={ menu.caption && ( {menu.caption} ) } /> {open ? ( ) : ( )} {menus} ) } NavCollapse.propTypes = { menu: PropTypes.object, level: PropTypes.number } export default NavCollapse ================================================ FILE: packages/ui/src/layout/MainLayout/Sidebar/MenuList/NavGroup/index.js ================================================ import PropTypes from 'prop-types' // material-ui import { useTheme } from '@mui/material/styles' import { Divider, List, Typography } from '@mui/material' // project imports import NavItem from '../NavItem' import NavCollapse from '../NavCollapse' // ==============================|| SIDEBAR MENU LIST GROUP ||============================== // const NavGroup = ({ item }) => { const theme = useTheme() // menu list collapse & items const items = item.children?.map((menu) => { switch (menu.type) { case 'collapse': return case 'item': return default: return ( Menu Items Error ) } }) return ( <> {item.title} {item.caption && ( {item.caption} )} ) } > {items} {/* group divider */} ) } NavGroup.propTypes = { item: PropTypes.object } export default NavGroup ================================================ FILE: packages/ui/src/layout/MainLayout/Sidebar/MenuList/NavItem/index.js ================================================ import PropTypes from 'prop-types' import { forwardRef, useEffect } from 'react' import { Link } from 'react-router-dom' import { useDispatch, useSelector } from 'react-redux' // material-ui import { useTheme } from '@mui/material/styles' import { Avatar, Chip, ListItemButton, ListItemIcon, ListItemText, Typography, useMediaQuery } from '@mui/material' // project imports import { MENU_OPEN, SET_MENU } from 'store/actions' import config from 'config' // assets import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord' // ==============================|| SIDEBAR MENU LIST ITEMS ||============================== // const NavItem = ({ item, level, navType, onClick, onUploadFile }) => { const theme = useTheme() const dispatch = useDispatch() const customization = useSelector((state) => state.customization) const matchesSM = useMediaQuery(theme.breakpoints.down('lg')) const Icon = item.icon const itemIcon = item?.icon ? ( ) : ( id === item?.id) > -1 ? 8 : 6, height: customization.isOpen.findIndex((id) => id === item?.id) > -1 ? 8 : 6 }} fontSize={level > 0 ? 'inherit' : 'medium'} /> ) let itemTarget = '_self' if (item.target) { itemTarget = '_blank' } let listItemProps = { component: forwardRef(function ListItemPropsComponent(props, ref) { return }) } if (item?.external) { listItemProps = { component: 'a', href: item.url, target: itemTarget } } if (item?.id === 'loadWorkflow') { listItemProps.component = 'label' } const handleFileUpload = (e) => { if (!e.target.files) return const file = e.target.files[0] const reader = new FileReader() reader.onload = (evt) => { if (!evt?.target?.result) { return } const { result } = evt.target onUploadFile(result) } reader.readAsText(file) } const itemHandler = (id) => { if (navType === 'SETTINGS' && id !== 'loadWorkflow') { onClick(id) } else { dispatch({ type: MENU_OPEN, id }) if (matchesSM) dispatch({ type: SET_MENU, opened: false }) } } // active menu item on page load useEffect(() => { if (navType === 'MENU') { const currentIndex = document.location.pathname .toString() .split('/') .findIndex((id) => id === item.id) if (currentIndex > -1) { dispatch({ type: MENU_OPEN, id: item.id }) } if (!document.location.pathname.toString().split('/')[1]) { itemHandler('workflows') } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [navType]) return ( 1 ? 'transparent !important' : 'inherit', py: level > 1 ? 1 : 1.25, pl: `${level * 24}px` }} selected={customization.isOpen.findIndex((id) => id === item.id) > -1} onClick={() => itemHandler(item.id)} > {item.id === 'loadWorkflow' && handleFileUpload(e)} />} {itemIcon} id === item.id) > -1 ? 'h5' : 'body1'} color='inherit'> {item.title} } secondary={ item.caption && ( {item.caption} ) } /> {item.chip && ( {item.chip.avatar}} /> )} ) } NavItem.propTypes = { item: PropTypes.object, level: PropTypes.number, navType: PropTypes.string, onClick: PropTypes.func, onUploadFile: PropTypes.func } export default NavItem ================================================ FILE: packages/ui/src/layout/MainLayout/Sidebar/MenuList/index.js ================================================ // material-ui import { Typography } from '@mui/material' // project imports import NavGroup from './NavGroup' import menuItem from 'menu-items' // ==============================|| SIDEBAR MENU LIST ||============================== // const MenuList = () => { const navItems = menuItem.items.map((item) => { switch (item.type) { case 'group': return default: return ( Menu Items Error ) } }) return <>{navItems} } export default MenuList ================================================ FILE: packages/ui/src/layout/MainLayout/Sidebar/index.js ================================================ import PropTypes from 'prop-types' // material-ui import { useTheme } from '@mui/material/styles' import { Box, Drawer, useMediaQuery } from '@mui/material' // third-party import PerfectScrollbar from 'react-perfect-scrollbar' import { BrowserView, MobileView } from 'react-device-detect' // project imports import MenuList from './MenuList' import LogoSection from '../LogoSection' import { drawerWidth } from 'store/constant' // ==============================|| SIDEBAR DRAWER ||============================== // const Sidebar = ({ drawerOpen, drawerToggle, window }) => { const theme = useTheme() const matchUpMd = useMediaQuery(theme.breakpoints.up('md')) const drawer = ( <> ) const container = window !== undefined ? () => window.document.body : undefined return ( {drawer} ) } Sidebar.propTypes = { drawerOpen: PropTypes.bool, drawerToggle: PropTypes.func, window: PropTypes.object } export default Sidebar ================================================ FILE: packages/ui/src/layout/MainLayout/index.js ================================================ import { useEffect } from 'react' import { useDispatch, useSelector } from 'react-redux' import { Outlet } from 'react-router-dom' // material-ui import { styled, useTheme } from '@mui/material/styles' import { AppBar, Box, CssBaseline, Toolbar, useMediaQuery } from '@mui/material' // project imports import Header from './Header' import Sidebar from './Sidebar' import { drawerWidth } from 'store/constant' import { SET_MENU } from 'store/actions' // styles const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({ ...theme.typography.mainContent, ...(!open && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0, transition: theme.transitions.create('margin', { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen }), [theme.breakpoints.up('md')]: { marginLeft: -(drawerWidth - 20), width: `calc(100% - ${drawerWidth}px)` }, [theme.breakpoints.down('md')]: { marginLeft: '20px', width: `calc(100% - ${drawerWidth}px)`, padding: '16px' }, [theme.breakpoints.down('sm')]: { marginLeft: '10px', width: `calc(100% - ${drawerWidth}px)`, padding: '16px', marginRight: '10px' } }), ...(open && { transition: theme.transitions.create('margin', { easing: theme.transitions.easing.easeOut, duration: theme.transitions.duration.enteringScreen }), marginLeft: 0, borderBottomLeftRadius: 0, borderBottomRightRadius: 0, width: `calc(100% - ${drawerWidth}px)`, [theme.breakpoints.down('md')]: { marginLeft: '20px' }, [theme.breakpoints.down('sm')]: { marginLeft: '10px' } }) })) // ==============================|| MAIN LAYOUT ||============================== // const MainLayout = () => { const theme = useTheme() const matchDownMd = useMediaQuery(theme.breakpoints.down('lg')) // Handle left drawer const leftDrawerOpened = useSelector((state) => state.customization.opened) const dispatch = useDispatch() const handleLeftDrawerToggle = () => { dispatch({ type: SET_MENU, opened: !leftDrawerOpened }) } useEffect(() => { dispatch({ type: SET_MENU, opened: !matchDownMd }) // eslint-disable-next-line react-hooks/exhaustive-deps }, [matchDownMd]) return ( {/* header */}
    {process.env.REACT_APP_IS_DEMO === 'true' && (

    This is a demo version to get an overview of Outerbridge. Beware of credentials you put in as everyone will be able to see it.

    )} {/* drawer */} {/* main content */}
    ) } export default MainLayout ================================================ FILE: packages/ui/src/layout/MinimalLayout/index.js ================================================ import { Outlet } from 'react-router-dom' // ==============================|| MINIMAL LAYOUT ||============================== // const MinimalLayout = () => ( <> ) export default MinimalLayout ================================================ FILE: packages/ui/src/layout/NavMotion.js ================================================ import PropTypes from 'prop-types' import { motion } from 'framer-motion' // ==============================|| ANIMATION FOR CONTENT ||============================== // const NavMotion = ({ children }) => { const motionVariants = { initial: { opacity: 0, scale: 0.99 }, in: { opacity: 1, scale: 1 }, out: { opacity: 0, scale: 1.01 } } const motionTransition = { type: 'tween', ease: 'anticipate', duration: 0.4 } return ( {children} ) } NavMotion.propTypes = { children: PropTypes.node } export default NavMotion ================================================ FILE: packages/ui/src/layout/NavigationScroll.js ================================================ import PropTypes from 'prop-types' import { useEffect } from 'react' import { useLocation } from 'react-router-dom' // ==============================|| NAVIGATION SCROLL TO TOP ||============================== // const NavigationScroll = ({ children }) => { const location = useLocation() const { pathname } = location useEffect(() => { window.scrollTo({ top: 0, left: 0, behavior: 'smooth' }) }, [pathname]) return children || null } NavigationScroll.propTypes = { children: PropTypes.node } export default NavigationScroll ================================================ FILE: packages/ui/src/menu-items/dashboard.js ================================================ // assets import { IconHierarchy, IconEditCircle, IconWallet, IconKey } from '@tabler/icons' // constant const icons = { IconHierarchy, IconEditCircle, IconWallet, IconKey } // ==============================|| DASHBOARD MENU ITEMS ||============================== // const dashboard = { id: 'dashboard', title: '', type: 'group', children: [ { id: 'workflows', title: 'Workflows', type: 'item', url: '/workflows', icon: icons.IconHierarchy, breadcrumbs: true }, { id: 'contracts', title: 'Contracts', type: 'item', url: '/contracts', icon: icons.IconEditCircle, breadcrumbs: true }, { id: 'wallets', title: 'Wallets', type: 'item', url: '/wallets', icon: icons.IconWallet, breadcrumbs: true }, { id: 'apikey', title: 'API Keys', type: 'item', url: '/apikey', icon: icons.IconKey, breadcrumbs: true } ] } export default dashboard ================================================ FILE: packages/ui/src/menu-items/index.js ================================================ import dashboard from './dashboard' // ==============================|| MENU ITEMS ||============================== // const menuItems = { items: [dashboard] } export default menuItems ================================================ FILE: packages/ui/src/menu-items/settings.js ================================================ // assets import { IconTrash, IconFileUpload, IconFileExport } from '@tabler/icons' // constant const icons = { IconTrash, IconFileUpload, IconFileExport } // ==============================|| SETTINGS MENU ITEMS ||============================== // const settings = { id: 'settings', title: '', type: 'group', children: [ { id: 'loadWorkflow', title: 'Load Workflow', type: 'item', url: '', icon: icons.IconFileUpload }, { id: 'exportWorkflow', title: 'Export Workflow', type: 'item', url: '', icon: icons.IconFileExport }, { id: 'deleteWorkflow', title: 'Delete Workflow', type: 'item', url: '', icon: icons.IconTrash } ] } export default settings ================================================ FILE: packages/ui/src/routes/CanvasRoutes.js ================================================ import { lazy } from 'react' // project imports import Loadable from 'ui-component/Loadable' import MinimalLayout from 'layout/MinimalLayout' // canvas routing const Canvas = Loadable(lazy(() => import('views/canvas'))) // ==============================|| CANVAS ROUTING ||============================== // const CanvasRoutes = { path: '/', element: , children: [ { path: '/canvas', element: }, { path: '/canvas/:id', element: } ] } export default CanvasRoutes ================================================ FILE: packages/ui/src/routes/MainRoutes.js ================================================ import { lazy } from 'react' // project imports import MainLayout from 'layout/MainLayout' import Loadable from 'ui-component/Loadable' // workflows routing const Workflows = Loadable(lazy(() => import('views/workflows'))) // contracts routing const Contracts = Loadable(lazy(() => import('views/contracts'))) // wallets routing const Wallets = Loadable(lazy(() => import('views/wallets'))) // apikey routing const APIKey = Loadable(lazy(() => import('views/apikey'))) // ==============================|| MAIN ROUTING ||============================== // const MainRoutes = { path: '/', element: , children: [ { path: '/', element: }, { path: '/workflows', element: }, { path: '/contracts', element: }, { path: '/wallets', element: }, { path: '/apikey', element: } ] } export default MainRoutes ================================================ FILE: packages/ui/src/routes/index.js ================================================ import { useRoutes } from 'react-router-dom' // routes import MainRoutes from './MainRoutes' import CanvasRoutes from './CanvasRoutes' import config from 'config' // ==============================|| ROUTING RENDER ||============================== // export default function ThemeRoutes() { return useRoutes([MainRoutes, CanvasRoutes], config.basename) } ================================================ FILE: packages/ui/src/serviceWorker.js ================================================ // This optional code is used to register a service worker. // register() is not called by default. // This lets the app load faster on subsequent visits in production, and gives // it offline capabilities. However, it also means that developers (and users) // will only see deployed updates on subsequent visits to a page, after all the // existing tabs open on the page have been closed, since previously cached // resources are updated in the background. // To learn more about the benefits of this model and instructions on how to // opt-in, read https://bit.ly/CRA-PWA const isLocalhost = Boolean( window.location.hostname === 'localhost' || // [::1] is the IPv6 localhost address. window.location.hostname === '[::1]' || // 127.0.0.0/8 are considered localhost for IPv4. window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/) ) function registerValidSW(swUrl, config) { navigator.serviceWorker .register(swUrl) .then((registration) => { registration.onupdatefound = () => { const installingWorker = registration.installing if (installingWorker == null) { return } installingWorker.onstatechange = () => { if (installingWorker.state === 'installed') { if (navigator.serviceWorker.controller) { // At this point, the updated precached content has been fetched, // but the previous service worker will still serve the older // content until all client tabs are closed. console.info( 'New content is available and will be used when all tabs for this page are closed. See https://bit.ly/CRA-PWA.' ) // Execute callback if (config && config.onUpdate) { config.onUpdate(registration) } } else { // At this point, everything has been precached. // It's the perfect time to display a // "Content is cached for offline use." message. console.info('Content is cached for offline use.') // Execute callback if (config && config.onSuccess) { config.onSuccess(registration) } } } } } }) .catch((error) => { console.error('Error during service worker registration:', error) }) } function checkValidServiceWorker(swUrl, config) { // Check if the service worker can be found. If it can't reload the page. fetch(swUrl, { headers: { 'Service-Worker': 'script' } }) .then((response) => { // Ensure service worker exists, and that we really are getting a JS file. const contentType = response.headers.get('content-type') if (response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1)) { // No service worker found. Probably a different app. Reload the page. navigator.serviceWorker.ready.then((registration) => { registration.unregister().then(() => { window.location.reload() }) }) } else { // Service worker found. Proceed as normal. registerValidSW(swUrl, config) } }) .catch(() => { console.info('No internet connection found. App is running in offline mode.') }) } export function register(config) { if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { // The URL constructor is available in all browsers that support SW. const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href) if (publicUrl.origin !== window.location.origin) { // Our service worker won't work if PUBLIC_URL is on a different origin // from what our page is served on. This might happen if a CDN is used to // serve assets; see https://github.com/facebook/create-react-app/issues/2374 return } window.addEventListener('load', () => { const swUrl = `${process.env.PUBLIC_URL}/service-worker.js` if (isLocalhost) { // This is running on localhost. Let's check if a service worker still exists or not. checkValidServiceWorker(swUrl, config) // Add some additional logging to localhost, pointing developers to the // service worker/PWA documentation. navigator.serviceWorker.ready.then(() => { console.info( 'This web app is being served cache-first by a service worker. To learn more, visit https://bit.ly/CRA-PWA' ) }) } else { // Is not localhost. Just register service worker registerValidSW(swUrl, config) } }) } } export function unregister() { if ('serviceWorker' in navigator) { navigator.serviceWorker.ready .then((registration) => { registration.unregister() }) .catch((error) => { console.error(error.message) }) } } ================================================ FILE: packages/ui/src/store/actions.js ================================================ // action - customization reducer export const SET_MENU = '@customization/SET_MENU' export const MENU_TOGGLE = '@customization/MENU_TOGGLE' export const MENU_OPEN = '@customization/MENU_OPEN' export const SET_FONT_FAMILY = '@customization/SET_FONT_FAMILY' export const SET_BORDER_RADIUS = '@customization/SET_BORDER_RADIUS' export const SET_LAYOUT = '@customization/SET_LAYOUT ' export const SET_DARKMODE = '@customization/SET_DARKMODE' // action - canvas reducer export const REMOVE_EDGE = '@canvas/REMOVE_EDGE' export const SET_DIRTY = '@canvas/SET_DIRTY' export const REMOVE_DIRTY = '@canvas/REMOVE_DIRTY' export const SET_WORKFLOW = '@canvas/SET_WORKFLOW' // action - notifier reducer export const ENQUEUE_SNACKBAR = 'ENQUEUE_SNACKBAR' export const CLOSE_SNACKBAR = 'CLOSE_SNACKBAR' export const REMOVE_SNACKBAR = 'REMOVE_SNACKBAR' // action - dialog reducer export const SHOW_CONFIRM = 'SHOW_CONFIRM' export const HIDE_CONFIRM = 'HIDE_CONFIRM' export const enqueueSnackbar = (notification) => { const key = notification.options && notification.options.key return { type: ENQUEUE_SNACKBAR, notification: { ...notification, key: key || new Date().getTime() + Math.random() } } } export const closeSnackbar = (key) => ({ type: CLOSE_SNACKBAR, dismissAll: !key, // dismiss all if no key has been defined key }) export const removeSnackbar = (key) => ({ type: REMOVE_SNACKBAR, key }) ================================================ FILE: packages/ui/src/store/constant.js ================================================ // constant export const gridSpacing = 3 export const drawerWidth = 260 export const appDrawerWidth = 320 export const baseURL = process.env.NODE_ENV === 'production' ? window.location.origin : window.location.origin.replace(':8080', ':3000') export const NETWORK = { MAINNET: 'homestead', GÖRLI: 'goerli', MATIC_MUMBAI: 'maticmum', MATIC: 'matic', OPTIMISM: 'optimism', OPTIMISM_GOERLI: 'optimism-goerli', ARBITRUM: 'arbitrum', ARBITRUM_GOERLI: 'arbitrum-goerli', ARBITRUM_NOVA: 'arbitrum-nova', BSC: 'bsc', BSC_TESTNET: 'bsc-testnet', AVALANCHE: 'avalanche', AVALANCHE_TESTNET: 'avalanche-testnet', FANTOM: 'fantom', FANTOM_TESTNET: 'fantom-testnet', CRONOS: 'cronos', CRONOS_TESTNET: 'cronos-testnet', GNOSIS: 'gnosis', CELO: 'celo', MOONRIVER: 'moonriver', MOONBEAM: 'moonbeam' } export const NETWORK_LABEL = { MAINNET: 'Mainnet', GÖRLI: 'Goerli', MATIC_MUMBAI: 'Polygon Mumbai', MATIC: 'Polygon Mainnet', OPTIMISM: 'Optimism Mainnet', OPTIMISM_GOERLI: 'Optimism Goerli', ARBITRUM: 'Arbitrum Mainnet', ARBITRUM_GOERLI: 'Arbitrum Goerli', ARBITRUM_NOVA: 'Arbitrum Nova', BSC: 'Binance Smart Chain Mainnet', BSC_TESTNET: 'Binance Smart Chain Testnet', AVALANCHE: 'Avalanche Mainnet', AVALANCHE_TESTNET: 'Avalanche Testnet', FANTOM: 'Fantom Mainnet', FANTOM_TESTNET: 'Fantom Testnet', CRONOS: 'Cronos Mainnet', CRONOS_TESTNET: 'Cronos Testnet', GNOSIS: 'Gnosis Mainnet', CELO: 'Celo Mainnet', MOONRIVER: 'Moonriver Mainnet', MOONBEAM: 'Moonbeam Mainnet' } export const scanAPIs = { [NETWORK.MAINNET]: 'https://api.etherscan.io/api', [NETWORK.GÖRLI]: 'https://api-goerli.etherscan.io/api', [NETWORK.MATIC]: 'https://api.polygonscan.com/api', [NETWORK.MATIC_MUMBAI]: 'https://api-testnet.polygonscan.com/api', [NETWORK.OPTIMISM]: 'https://api-optimistic.etherscan.io/api', [NETWORK.OPTIMISM_GOERLI]: 'https://api-goerli-optimistic.etherscan.io/api', [NETWORK.ARBITRUM]: 'https://api.arbiscan.io/api', [NETWORK.ARBITRUM_GOERLI]: 'https://api-goerli.arbiscan.io/api', [NETWORK.BSC]: 'https://api.bscscan.com/api', [NETWORK.BSC_TESTNET]: 'https://api-testnet.bscscan.com/api', [NETWORK.AVALANCHE]: 'https://api.snowtrace.io/api', [NETWORK.AVALANCHE_TESTNET]: 'https://api-testnet.snowtrace.io/api', [NETWORK.FANTOM]: 'https://api.ftmscan.com/api', [NETWORK.FANTOM_TESTNET]: 'https://api-testnet.ftmscan.com/api', [NETWORK.CRONOS]: 'https://api.cronoscan.com/api', [NETWORK.CRONOS_TESTNET]: 'https://api-testnet.cronoscan.com/api', [NETWORK.GNOSIS]: 'https://api.gnosisscan.io/api', [NETWORK.CELO]: 'https://api.celoscan.io/api', [NETWORK.MOONRIVER]: 'https://api-moonriver.moonscan.io/api', [NETWORK.MOONBEAM]: 'https://api-moonbeam.moonscan.io/api' } export const networkExplorers = { [NETWORK.MAINNET]: 'https://etherscan.io', [NETWORK.GÖRLI]: 'https://goerli.etherscan.io', [NETWORK.MATIC]: 'https://polygonscan.com', [NETWORK.MATIC_MUMBAI]: 'https://mumbai.polygonscan.com', [NETWORK.OPTIMISM]: 'https://optimistic.etherscan.io', [NETWORK.OPTIMISM_GOERLI]: 'https://goerli-optimistic.etherscan.io', [NETWORK.ARBITRUM]: 'https://arbiscan.io', [NETWORK.ARBITRUM_GOERLI]: 'https://goerli-explorer.arbitrum.io', [NETWORK.BSC]: 'https://bscscan.com', [NETWORK.BSC_TESTNET]: 'https://testnet.bscscan.com', [NETWORK.FANTOM]: 'https://ftmscan.com', [NETWORK.FANTOM_TESTNET]: 'https://testnet.ftmscan.com', [NETWORK.CRONOS]: 'https://cronoscan.com', [NETWORK.CRONOS_TESTNET]: 'https://testnet.cronoscan.com', [NETWORK.GNOSIS]: 'https://gnosisscan.io', [NETWORK.CELO]: 'https://celoscan.io', [NETWORK.MOONRIVER]: 'https://moonriver.moonscan.io', [NETWORK.MOONBEAM]: 'https://moonscan.io' } export const networks = [ { label: NETWORK_LABEL.MAINNET, name: NETWORK.MAINNET, uri: scanAPIs[NETWORK.MAINNET], color: '#3c3c3d' }, { label: NETWORK_LABEL.GÖRLI, name: NETWORK.GÖRLI, uri: scanAPIs[NETWORK.GÖRLI], color: '#3a86ff' }, { label: NETWORK_LABEL.MATIC, name: NETWORK.MATIC, uri: scanAPIs[NETWORK.MATIC], color: '#8247e5' }, { label: NETWORK_LABEL.MATIC_MUMBAI, name: NETWORK.MATIC_MUMBAI, uri: scanAPIs[NETWORK.MATIC_MUMBAI], color: '#8247e5' }, { label: NETWORK_LABEL.BSC, name: NETWORK.BSC, uri: scanAPIs[NETWORK.BSC], color: '#ffbe0b' }, { label: NETWORK_LABEL.BSC_TESTNET, name: NETWORK.BSC_TESTNET, uri: scanAPIs[NETWORK.BSC_TESTNET], color: '#ffbe0b' }, { label: NETWORK_LABEL.OPTIMISM, name: NETWORK.OPTIMISM, uri: scanAPIs[NETWORK.OPTIMISM], color: '#ef233c' }, { label: NETWORK_LABEL.OPTIMISM_GOERLI, name: NETWORK.OPTIMISM_GOERLI, uri: scanAPIs[NETWORK.OPTIMISM_GOERLI], color: '#ef233c' }, { label: NETWORK_LABEL.ARBITRUM, name: NETWORK.ARBITRUM, uri: scanAPIs[NETWORK.ARBITRUM], color: '#023e8a' }, { label: NETWORK_LABEL.ARBITRUM_GOERLI, name: NETWORK.ARBITRUM_GOERLI, uri: scanAPIs[NETWORK.ARBITRUM_GOERLI], color: '#023e8a' }, { label: NETWORK_LABEL.AVALANCHE, name: NETWORK.AVALANCHE, uri: scanAPIs[NETWORK.AVALANCHE], color: '#e84142' }, { label: NETWORK_LABEL.AVALANCHE_TESTNET, name: NETWORK.AVALANCHE_TESTNET, uri: scanAPIs[NETWORK.AVALANCHE_TESTNET], color: '#e84142' }, { label: NETWORK_LABEL.FANTOM, name: NETWORK.FANTOM, uri: scanAPIs[NETWORK.FANTOM], color: '#001f68' }, { label: NETWORK_LABEL.FANTOM_TESTNET, name: NETWORK.FANTOM_TESTNET, uri: scanAPIs[NETWORK.FANTOM_TESTNET], color: '#001f68' }, { label: NETWORK_LABEL.CRONOS, name: NETWORK.CRONOS, uri: scanAPIs[NETWORK.CRONOS], color: '#0c1526' }, { label: NETWORK_LABEL.CRONOS_TESTNET, name: NETWORK.CRONOS_TESTNET, uri: scanAPIs[NETWORK.CRONOS_TESTNET], color: '#0c1526' }, { label: NETWORK_LABEL.GNOSIS, name: NETWORK.GNOSIS, uri: scanAPIs[NETWORK.GNOSIS], color: '#0d8e74' }, { label: NETWORK_LABEL.CELO, name: NETWORK.CELO, uri: scanAPIs[NETWORK.CELO], color: '#35d07f' }, { label: NETWORK_LABEL.MOONRIVER, name: NETWORK.MOONRIVER, uri: scanAPIs[NETWORK.MOONRIVER], color: '#fcbf49' }, { label: NETWORK_LABEL.MOONBEAM, name: NETWORK.MOONBEAM, uri: scanAPIs[NETWORK.MOONBEAM], color: '#f15bb5' } ] export const network_details = { networks: [ { label: 'Select Network', name: 'network', type: 'options', options: networks, default: 'homestead' } ], credentials: [ { label: 'API Key (Optional)', name: 'credentialMethod', type: 'options', description: 'Provide an API key to avoid rate limit', options: [ { label: 'No Auth', name: 'noAuth', description: 'Use public endpoint without API key', hideRegisteredCredential: true }, { label: 'Etherscan API', name: 'etherscanApi', show: { 'networks.network': [NETWORK.MAINNET, NETWORK.GÖRLI] }, description: `Register for a Free API Key from: ${networkExplorers[NETWORK.MAINNET]}/apis` }, { label: 'Polygonscan API', name: 'polygonscanApi', show: { 'networks.network': [NETWORK.MATIC, NETWORK.MATIC_MUMBAI] }, description: `Register for a Free API Key from: ${networkExplorers[NETWORK.MATIC]}/apis` }, { label: 'Bscscan API', name: 'bscscanApi', show: { 'networks.network': [NETWORK.BSC, NETWORK.BSC_TESTNET] }, description: `Register for a Free API Key from: ${networkExplorers[NETWORK.BSC]}/apis` }, { label: 'Optimism Etherscan API', name: 'optimisticEtherscanApi', show: { 'networks.network': [NETWORK.OPTIMISM, NETWORK.OPTIMISM_GOERLI] }, description: `Register for a Free API Key from: ${networkExplorers[NETWORK.OPTIMISM]}/apis` }, { label: 'Arbiscan API', name: 'arbiscanApi', show: { 'networks.network': [NETWORK.ARBITRUM, NETWORK.ARBITRUM_GOERLI] }, description: `Register for a Free API Key from: ${networkExplorers[NETWORK.ARBITRUM]}/apis` }, { label: 'FantomScan API', name: 'fantomscanApi', show: { 'networks.network': [NETWORK.FANTOM, NETWORK.FANTOM_TESTNET] }, description: `Register for a Free API Key from: ${networkExplorers[NETWORK.FANTOM]}/apis` }, { label: 'SnowTrace API', name: 'snowtraceApi', show: { 'networks.network': [NETWORK.AVALANCHE, NETWORK.AVALANCHE_TESTNET] }, description: `Register for a Free API Key from: ${networkExplorers[NETWORK.AVALANCHE]}/apis` }, { label: 'CronosScan API', name: 'cronosscanApi', show: { 'networks.network': [NETWORK.CRONOS, NETWORK.CRONOS_TESTNET] }, description: `Register for a Free API Key from: ${networkExplorers[NETWORK.CRONOS]}/apis` }, { label: 'GnosisScan API', name: 'gnosisscanApi', show: { 'networks.network': [NETWORK.GNOSIS] }, description: `Register for a Free API Key from: ${networkExplorers[NETWORK.GNOSIS]}/apis` }, { label: 'CeloScan API', name: 'celoscanApi', show: { 'networks.network': [NETWORK.CELO] }, description: `Register for a Free API Key from: ${networkExplorers[NETWORK.CELO]}/apis` }, { label: 'MoonRiverScan API', name: 'moonRiverScanApi', show: { 'networks.network': [NETWORK.MOONRIVER] }, description: `Register for a Free API Key from: ${networkExplorers[NETWORK.MOONRIVER]}/apis` }, { label: 'MoonbeamScan API', name: 'moonBeamScanApi', show: { 'networks.network': [NETWORK.MOONBEAM] }, description: `Register for a Free API Key from: ${networkExplorers[NETWORK.MOONBEAM]}/apis` } ], default: 'noAuth' } ] } export const contract_details = { ...network_details, contractInfo: [ { label: 'Contract Name', name: 'name', type: 'string', description: 'Name the contract to make it easier to identify it in Outerbridge', default: '' }, { label: 'Contract Address', name: 'address', type: 'string', default: '' }, { label: 'ABI', name: 'abi', type: 'json', default: '', description: 'ABI will be fetched automatically if address is valid' } ] } export const wallet_details = { ...network_details, walletInfo: [ { label: 'Wallet Name', name: 'name', type: 'string', description: 'Name the wallet to make it easier to identify it in Outerbridge', default: '' } ] } export const privateKeyField = [ { label: 'Private Key', name: 'privateKey', type: 'string', description: 'Private key of wallet to be imported', default: '' } ] ================================================ FILE: packages/ui/src/store/context/ConfirmContext.js ================================================ import React from 'react' const ConfirmContext = React.createContext() export default ConfirmContext ================================================ FILE: packages/ui/src/store/context/ConfirmContextProvider.js ================================================ import { useReducer } from 'react' import PropTypes from 'prop-types' import alertReducer, { initialState } from '../reducers/dialogReducer' import ConfirmContext from './ConfirmContext' const ConfirmContextProvider = ({ children }) => { const [state, dispatch] = useReducer(alertReducer, initialState) return {children} } ConfirmContextProvider.propTypes = { children: PropTypes.any } export default ConfirmContextProvider ================================================ FILE: packages/ui/src/store/index.js ================================================ import { createStore } from 'redux' import reducer from './reducer' // ==============================|| REDUX - MAIN STORE ||============================== // const store = createStore(reducer) const persister = 'Free' export { store, persister } ================================================ FILE: packages/ui/src/store/reducer.js ================================================ import { combineReducers } from 'redux' // reducer import import customizationReducer from './reducers/customizationReducer' import canvasReducer from './reducers/canvasReducer' import notifierReducer from './reducers/notifierReducer' import dialogReducer from './reducers/dialogReducer' // ==============================|| COMBINE REDUCER ||============================== // const reducer = combineReducers({ customization: customizationReducer, canvas: canvasReducer, notifier: notifierReducer, dialog: dialogReducer }) export default reducer ================================================ FILE: packages/ui/src/store/reducers/canvasReducer.js ================================================ // action - state management import * as actionTypes from '../actions' export const initialState = { removeEdgeId: '', isDirty: false, workflow: null } // ==============================|| CANVAS REDUCER ||============================== // const canvasReducer = (state = initialState, action) => { switch (action.type) { case actionTypes.REMOVE_EDGE: return { ...state, removeEdgeId: action.edgeId } case actionTypes.SET_DIRTY: return { ...state, isDirty: true } case actionTypes.REMOVE_DIRTY: return { ...state, isDirty: false } case actionTypes.SET_WORKFLOW: return { ...state, workflow: action.workflow } default: return state } } export default canvasReducer ================================================ FILE: packages/ui/src/store/reducers/customizationReducer.js ================================================ // project imports import config from 'config' // action - state management import * as actionTypes from '../actions' export const initialState = { isOpen: [], // for active default menu fontFamily: config.fontFamily, borderRadius: config.borderRadius, opened: true, isHorizontal: localStorage.getItem('isHorizontal') === 'true' ? true : false, isDarkMode: localStorage.getItem('isDarkMode') === 'true' ? true : false } // ==============================|| CUSTOMIZATION REDUCER ||============================== // const customizationReducer = (state = initialState, action) => { let id switch (action.type) { case actionTypes.MENU_OPEN: id = action.id return { ...state, isOpen: [id] } case actionTypes.SET_MENU: return { ...state, opened: action.opened } case actionTypes.SET_FONT_FAMILY: return { ...state, fontFamily: action.fontFamily } case actionTypes.SET_BORDER_RADIUS: return { ...state, borderRadius: action.borderRadius } case actionTypes.SET_LAYOUT: return { ...state, isHorizontal: action.isHorizontal } case actionTypes.SET_DARKMODE: return { ...state, isDarkMode: action.isDarkMode } default: return state } } export default customizationReducer ================================================ FILE: packages/ui/src/store/reducers/dialogReducer.js ================================================ import { SHOW_CONFIRM, HIDE_CONFIRM } from '../actions' export const initialState = { show: false, title: '', description: '', confirmButtonName: 'OK', cancelButtonName: 'Cancel' } const alertReducer = (state = initialState, action) => { switch (action.type) { case SHOW_CONFIRM: return { show: true, title: action.payload.title, description: action.payload.description, confirmButtonName: action.payload.confirmButtonName, cancelButtonName: action.payload.cancelButtonName } case HIDE_CONFIRM: return initialState default: return state } } export default alertReducer ================================================ FILE: packages/ui/src/store/reducers/notifierReducer.js ================================================ import { ENQUEUE_SNACKBAR, CLOSE_SNACKBAR, REMOVE_SNACKBAR } from '../actions' export const initialState = { notifications: [] } const notifierReducer = (state = initialState, action) => { switch (action.type) { case ENQUEUE_SNACKBAR: return { ...state, notifications: [ ...state.notifications, { key: action.key, ...action.notification } ] } case CLOSE_SNACKBAR: return { ...state, notifications: state.notifications.map((notification) => action.dismissAll || notification.key === action.key ? { ...notification, dismissed: true } : { ...notification } ) } case REMOVE_SNACKBAR: return { ...state, notifications: state.notifications.filter((notification) => notification.key !== action.key) } default: return state } } export default notifierReducer ================================================ FILE: packages/ui/src/themes/compStyleOverride.js ================================================ export default function componentStyleOverrides(theme) { const bgColor = theme.colors?.grey50 return { MuiButton: { styleOverrides: { root: { fontWeight: 500, borderRadius: '4px' } } }, MuiSvgIcon: { styleOverrides: { root: { color: theme?.customization?.isDarkMode ? theme.colors?.paper : 'inherit', background: theme?.customization?.isDarkMode ? theme.colors?.darkPrimaryLight : 'inherit' } } }, MuiPaper: { defaultProps: { elevation: 0 }, styleOverrides: { root: { backgroundImage: 'none' }, rounded: { borderRadius: `${theme?.customization?.borderRadius}px` } } }, MuiCardHeader: { styleOverrides: { root: { color: theme.colors?.textDark, padding: '24px' }, title: { fontSize: '1.125rem' } } }, MuiCardContent: { styleOverrides: { root: { padding: '24px' } } }, MuiCardActions: { styleOverrides: { root: { padding: '24px' } } }, MuiListItemButton: { styleOverrides: { root: { color: theme.darkTextPrimary, paddingTop: '10px', paddingBottom: '10px', '&.Mui-selected': { color: theme.menuSelected, backgroundColor: theme.menuSelectedBack, '&:hover': { backgroundColor: theme.menuSelectedBack }, '& .MuiListItemIcon-root': { color: theme.menuSelected } }, '&:hover': { backgroundColor: theme.menuSelectedBack, color: theme.menuSelected, '& .MuiListItemIcon-root': { color: theme.menuSelected } } } } }, MuiListItemIcon: { styleOverrides: { root: { color: theme.darkTextPrimary, minWidth: '36px' } } }, MuiListItemText: { styleOverrides: { primary: { color: theme.textDark } } }, MuiInputBase: { styleOverrides: { input: { color: theme.textDark, '&::placeholder': { color: theme.darkTextSecondary, fontSize: '0.875rem' } } } }, MuiOutlinedInput: { styleOverrides: { root: { background: theme?.customization?.isDarkMode ? theme.colors?.darkPrimary800 : bgColor, borderRadius: `${theme?.customization?.borderRadius}px`, '& .MuiOutlinedInput-notchedOutline': { borderColor: theme.colors?.grey400 }, '&:hover $notchedOutline': { borderColor: theme.colors?.primaryLight }, '&.MuiInputBase-multiline': { padding: 1 } }, input: { fontWeight: 500, background: theme?.customization?.isDarkMode ? theme.colors?.darkPrimary800 : bgColor, padding: '15.5px 14px', borderRadius: `${theme?.customization?.borderRadius}px`, '&.MuiInputBase-inputSizeSmall': { padding: '10px 14px', '&.MuiInputBase-inputAdornedStart': { paddingLeft: 0 } } }, inputAdornedStart: { paddingLeft: 4 }, notchedOutline: { borderRadius: `${theme?.customization?.borderRadius}px` } } }, MuiSlider: { styleOverrides: { root: { '&.Mui-disabled': { color: theme.colors?.grey300 } }, mark: { backgroundColor: theme.paper, width: '4px' }, valueLabel: { color: theme?.colors?.primaryLight } } }, MuiDivider: { styleOverrides: { root: { borderColor: theme.divider, opacity: 1 } } }, MuiAvatar: { styleOverrides: { root: { color: theme.colors?.primaryDark, background: theme.colors?.primary200 } } }, MuiChip: { styleOverrides: { root: { '&.MuiChip-deletable .MuiChip-deleteIcon': { color: 'inherit' } } } }, MuiTooltip: { styleOverrides: { tooltip: { color: theme?.customization?.isDarkMode ? theme.colors?.paper : theme.paper, background: theme.colors?.grey700 } } }, MuiAutocomplete: { styleOverrides: { option: { '&:hover': { background: theme?.customization?.isDarkMode ? '#233345 !important' : '' } } } } } } ================================================ FILE: packages/ui/src/themes/index.js ================================================ import { createTheme } from '@mui/material/styles' // assets import colors from 'assets/scss/_themes-vars.module.scss' // project imports import componentStyleOverrides from './compStyleOverride' import themePalette from './palette' import themeTypography from './typography' /** * Represent theme style and structure as per Material-UI * @param {JsonObject} customization customization parameter object */ export const theme = (customization) => { const color = colors const themeOption = customization.isDarkMode ? { colors: color, heading: color.paper, paper: color.darkPrimaryLight, backgroundDefault: color.darkPaper, background: color.darkPrimaryLight, darkTextPrimary: color.paper, darkTextSecondary: color.paper, textDark: color.paper, menuSelected: color.darkSecondaryDark, menuSelectedBack: color.darkSecondaryLight, divider: color.darkPaper, customization } : { colors: color, heading: color.grey900, paper: color.paper, backgroundDefault: color.paper, background: color.primaryLight, darkTextPrimary: color.grey700, darkTextSecondary: color.grey500, textDark: color.grey900, menuSelected: color.secondaryDark, menuSelectedBack: color.secondaryLight, divider: color.grey200, customization } const themeOptions = { direction: 'ltr', palette: themePalette(themeOption), mixins: { toolbar: { minHeight: '48px', padding: '16px', '@media (min-width: 600px)': { minHeight: '48px' } } }, typography: themeTypography(themeOption) } const themes = createTheme(themeOptions) themes.components = componentStyleOverrides(themeOption) return themes } export default theme ================================================ FILE: packages/ui/src/themes/palette.js ================================================ /** * Color intention that you want to used in your theme * @param {JsonObject} theme Theme customization object */ export default function themePalette(theme) { return { mode: theme?.customization?.navType, common: { black: theme.colors?.darkPaper }, primary: { light: theme.customization.isDarkMode ? theme.colors?.darkPrimaryLight : theme.colors?.primaryLight, main: theme.colors?.primaryMain, dark: theme.customization.isDarkMode ? theme.colors?.darkPrimaryDark : theme.colors?.primaryDark, 200: theme.customization.isDarkMode ? theme.colors?.darkPrimary200 : theme.colors?.primary200, 800: theme.customization.isDarkMode ? theme.colors?.darkPrimary800 : theme.colors?.primary800 }, secondary: { light: theme.customization.isDarkMode ? theme.colors?.darkSecondaryLight : theme.colors?.secondaryLight, main: theme.customization.isDarkMode ? theme.colors?.darkSecondaryMain : theme.colors?.secondaryMain, dark: theme.customization.isDarkMode ? theme.colors?.darkSecondaryDark : theme.colors?.secondaryDark, 200: theme.colors?.secondary200, 800: theme.colors?.secondary800 }, error: { light: theme.colors?.errorLight, main: theme.colors?.errorMain, dark: theme.colors?.errorDark }, orange: { light: theme.colors?.orangeLight, main: theme.colors?.orangeMain, dark: theme.colors?.orangeDark }, warning: { light: theme.colors?.warningLight, main: theme.colors?.warningMain, dark: theme.colors?.warningDark }, success: { light: theme.colors?.successLight, 200: theme.colors?.success200, main: theme.colors?.successMain, dark: theme.colors?.successDark }, grey: { 50: theme.colors?.grey50, 100: theme.colors?.grey100, 200: theme.colors?.grey200, 300: theme.colors?.grey300, 500: theme.darkTextSecondary, 600: theme.heading, 700: theme.darkTextPrimary, 900: theme.textDark }, dark: { light: theme.colors?.darkTextPrimary, main: theme.colors?.darkLevel1, dark: theme.colors?.darkLevel2, 800: theme.colors?.darkBackground, 900: theme.colors?.darkPaper }, text: { primary: theme.darkTextPrimary, secondary: theme.darkTextSecondary, dark: theme.textDark, hint: theme.colors?.grey100 }, background: { paper: theme.paper, default: theme.backgroundDefault }, card: { main: theme.customization.isDarkMode ? theme.colors?.darkPrimaryMain : theme.colors?.paper, light: theme.customization.isDarkMode ? theme.colors?.darkPrimary200 : theme.colors?.paper, hover: theme.customization.isDarkMode ? theme.colors?.darkPrimary800 : theme.colors?.paper }, asyncSelect: { main: theme.customization.isDarkMode ? theme.colors?.darkPrimary800 : theme.colors?.grey50 }, canvasHeader: { executionLight: theme.colors?.successLight, executionDark: theme.colors?.successDark, deployLight: theme.colors?.primaryLight, deployDark: theme.colors?.primaryDark, saveLight: theme.colors?.secondaryLight, saveDark: theme.colors?.secondaryDark, settingsLight: theme.colors?.grey300, settingsDark: theme.colors?.grey700 }, codeEditor: { main: theme.customization.isDarkMode ? theme.colors?.darkPrimary800 : theme.colors?.primaryLight } } } ================================================ FILE: packages/ui/src/themes/typography.js ================================================ /** * Typography used in theme * @param {JsonObject} theme theme customization object */ export default function themeTypography(theme) { return { fontFamily: theme?.customization?.fontFamily, h6: { fontWeight: 500, color: theme.heading, fontSize: '0.75rem' }, h5: { fontSize: '0.875rem', color: theme.heading, fontWeight: 500 }, h4: { fontSize: '1rem', color: theme.heading, fontWeight: 600 }, h3: { fontSize: '1.25rem', color: theme.heading, fontWeight: 600 }, h2: { fontSize: '1.5rem', color: theme.heading, fontWeight: 700 }, h1: { fontSize: '2.125rem', color: theme.heading, fontWeight: 700 }, subtitle1: { fontSize: '0.875rem', fontWeight: 500, color: theme.textDark }, subtitle2: { fontSize: '0.75rem', fontWeight: 400, color: theme.darkTextSecondary }, caption: { fontSize: '0.75rem', color: theme.darkTextSecondary, fontWeight: 400 }, body1: { fontSize: '0.875rem', fontWeight: 400, lineHeight: '1.334em' }, body2: { letterSpacing: '0em', fontWeight: 400, lineHeight: '1.5em', color: theme.darkTextPrimary }, button: { textTransform: 'capitalize' }, customInput: { marginTop: 1, marginBottom: 1, '& > label': { top: 23, left: 0, color: theme.grey500, '&[data-shrink="false"]': { top: 5 } }, '& > div > input': { padding: '30.5px 14px 11.5px !important' }, '& legend': { display: 'none' }, '& fieldset': { top: 0 } }, mainContent: { backgroundColor: theme.background, width: '100%', minHeight: 'calc(100vh - 75px)', flexGrow: 1, padding: '20px', marginTop: '75px', marginRight: '20px', borderRadius: `${theme?.customization?.borderRadius}px` }, menuCaption: { fontSize: '0.875rem', fontWeight: 500, color: theme.heading, padding: '6px', textTransform: 'capitalize', marginTop: '10px' }, subMenuCaption: { fontSize: '0.6875rem', fontWeight: 500, color: theme.darkTextSecondary, textTransform: 'capitalize' }, commonAvatar: { cursor: 'pointer', borderRadius: '8px' }, smallAvatar: { width: '22px', height: '22px', fontSize: '1rem' }, mediumAvatar: { width: '34px', height: '34px', fontSize: '1.2rem' }, largeAvatar: { width: '44px', height: '44px', fontSize: '1.5rem' } } } ================================================ FILE: packages/ui/src/ui-component/Loadable.js ================================================ import { Suspense } from 'react' // project imports import Loader from './Loader' // ==============================|| LOADABLE - LAZY LOADING ||============================== // const Loadable = (Component) => function WithLoader(props) { return ( }> ) } export default Loadable ================================================ FILE: packages/ui/src/ui-component/Loader.js ================================================ // material-ui import LinearProgress from '@mui/material/LinearProgress' import { styled } from '@mui/material/styles' // styles const LoaderWrapper = styled('div')({ position: 'fixed', top: 0, left: 0, zIndex: 1301, width: '100%' }) // ==============================|| LOADER ||============================== // const Loader = () => ( ) export default Loader ================================================ FILE: packages/ui/src/ui-component/Logo.js ================================================ import logo from 'assets/images/outerbridge_brand.png' import logoDark from 'assets/images/outerbridge_brand_white.png' import { useSelector } from 'react-redux' // ==============================|| LOGO ||============================== // const Logo = () => { const customization = useSelector((state) => state.customization) return (
    Outerbridge
    ) } export default Logo ================================================ FILE: packages/ui/src/ui-component/StyledButton.js ================================================ import { styled } from '@mui/material/styles' import { Button } from '@mui/material' export const StyledButton = styled(Button)(({ theme, color = 'primary' }) => ({ color: 'white', backgroundColor: theme.palette[color].main, '&:hover': { backgroundColor: theme.palette[color].main, backgroundImage: `linear-gradient(rgb(0 0 0/10%) 0 0)` } })) ================================================ FILE: packages/ui/src/ui-component/StyledFab.js ================================================ import { styled } from '@mui/material/styles' import { Fab } from '@mui/material' export const StyledFab = styled(Fab)(({ theme, color = 'primary' }) => ({ color: 'white', backgroundColor: theme.palette[color].main, '&:hover': { backgroundColor: theme.palette[color].main, backgroundImage: `linear-gradient(rgb(0 0 0/10%) 0 0)` } })) ================================================ FILE: packages/ui/src/ui-component/TooltipWithParser.js ================================================ import { Info } from '@mui/icons-material' import { IconButton, Tooltip } from '@mui/material' import parser from 'html-react-parser' import PropTypes from 'prop-types' import { useSelector } from 'react-redux' export const TooltipWithParser = ({ title }) => { const customization = useSelector((state) => state.customization) return (
    ) } TooltipWithParser.propTypes = { title: PropTypes.node } ================================================ FILE: packages/ui/src/ui-component/cards/ItemCard.js ================================================ import PropTypes from 'prop-types' // material-ui import { styled, useTheme } from '@mui/material/styles' import { Box, Grid, Chip, Typography } from '@mui/material' // project imports import MainCard from 'ui-component/cards/MainCard' import SkeletonWorkflowCard from 'ui-component/cards/Skeleton/WorkflowCard' // Const import { networks } from 'store/constant' import Jazzicon, { jsNumberForAddress } from 'react-jazzicon' const CardWrapper = styled(MainCard)(({ theme }) => ({ background: theme.palette.card.main, color: theme.darkTextPrimary, overflow: 'hidden', position: 'relative', boxShadow: '0 2px 14px 0 rgb(32 40 45 / 8%)', cursor: 'pointer', '&:hover': { background: theme.palette.card.hover, boxShadow: '0 2px 14px 0 rgb(32 40 45 / 20%)' } })) // ===========================|| CONTRACT CARD ||=========================== // const ItemCard = ({ isLoading, data, images, onClick }) => { const theme = useTheme() const chipSX = { height: 24, padding: '0 6px' } const activeWorkflowSX = { ...chipSX, color: 'white', backgroundColor: theme.palette.success.dark } const getNetworkItem = (network) => { return networks.find((ntw) => ntw.name === network) } return ( <> {isLoading ? ( ) : (
    {data.address && (
    )} {data.name}
    {data.address && ( {`${data.address.substring(0, 8)}...${data.address.slice(-4)}`} )} {data.flowData && ( Total Executions: {data.executionCount || '0'} )} {data.deployed && ( )} {data.network && ( )} {images && (
    {images.map((img) => (
    ))}
    )}
    )} ) } ItemCard.propTypes = { isLoading: PropTypes.bool, data: PropTypes.object, images: PropTypes.array, onClick: PropTypes.func } export default ItemCard ================================================ FILE: packages/ui/src/ui-component/cards/MainCard.js ================================================ import PropTypes from 'prop-types' import { forwardRef } from 'react' // material-ui import { useTheme } from '@mui/material/styles' import { Card, CardContent, CardHeader, Divider, Typography } from '@mui/material' // constant const headerSX = { '& .MuiCardHeader-action': { mr: 0 } } // ==============================|| CUSTOM MAIN CARD ||============================== // const MainCard = forwardRef(function MainCard( { border = true, boxShadow, children, content = true, contentClass = '', contentSX = {}, darkTitle, secondary, shadow, sx = {}, title, ...others }, ref ) { const theme = useTheme() return ( {/* card header and action */} {!darkTitle && title && } {darkTitle && title && {title}} action={secondary} />} {/* content & header divider */} {title && } {/* card content */} {content && ( {children} )} {!content && children} ) }) MainCard.propTypes = { border: PropTypes.bool, boxShadow: PropTypes.bool, children: PropTypes.node, content: PropTypes.bool, contentClass: PropTypes.string, contentSX: PropTypes.object, darkTitle: PropTypes.bool, secondary: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.object]), shadow: PropTypes.string, sx: PropTypes.object, title: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.object]) } export default MainCard ================================================ FILE: packages/ui/src/ui-component/cards/Skeleton/WorkflowCard.js ================================================ // material-ui import { Card, CardContent, Grid } from '@mui/material' import Skeleton from '@mui/material/Skeleton' // ==============================|| SKELETON - BRIDGE CARD ||============================== // const WorkflowCard = () => ( ) export default WorkflowCard ================================================ FILE: packages/ui/src/ui-component/dialog/AttachmentDialog.js ================================================ import { createPortal } from 'react-dom' import { useState } from 'react' import PropTypes from 'prop-types' import { Dialog, DialogContent, DialogTitle, Tabs, Tab, Box, Typography } from '@mui/material' function TabPanel(props) { const { children, value, index, ...other } = props return ( ) } TabPanel.propTypes = { children: PropTypes.node, index: PropTypes.number.isRequired, value: PropTypes.number.isRequired } function a11yProps(index) { return { id: `attachment-tab-${index}`, 'aria-controls': `attachment-tabpanel-${index}` } } const AttachmentDialog = ({ show, dialogProps, onCancel }) => { const portalElement = document.getElementById('portal') const [value, setValue] = useState(0) const handleChange = (event, newValue) => { setValue(newValue) } const formatBytes = (bytes, decimals = 2) => { if (bytes === 0) return '0 Bytes' const k = 1024 const dm = decimals < 0 ? 0 : decimals const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] const i = Math.floor(Math.log(bytes) / Math.log(k)) return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i] } const component = show ? ( {dialogProps.title} {dialogProps.executionData.map((execObj, execObjIndex) => ( ))} {dialogProps.executionData.map((execObj, execObjIndex) => ( {execObj.attachments && execObj.attachments.map((attachment, attchIndex) => (
    {attachment.filename ? attachment.filename : `Attachment ${attchIndex}`} |{' '} {attachment.contentType} {attachment.size ? ` | ${formatBytes(attachment.size)}` : ''} Download File
    ))} {!execObj.attachments &&
    No Attachment
    }
    ))}
    ) : null return createPortal(component, portalElement) } AttachmentDialog.propTypes = { show: PropTypes.bool, dialogProps: PropTypes.object, onCancel: PropTypes.func } export default AttachmentDialog ================================================ FILE: packages/ui/src/ui-component/dialog/ConfirmDialog.js ================================================ import { createPortal } from 'react-dom' import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from '@mui/material' import useConfirm from 'hooks/useConfirm' import { StyledButton } from 'ui-component/StyledButton' const ConfirmDialog = () => { const { onConfirm, onCancel, confirmState } = useConfirm() const portalElement = document.getElementById('portal') const component = confirmState.show ? ( {confirmState.title} {confirmState.description} {confirmState.confirmButtonName} ) : null return createPortal(component, portalElement) } export default ConfirmDialog ================================================ FILE: packages/ui/src/ui-component/dialog/EditVariableDialog.css ================================================ .editor__textarea { outline: 0; } .editor__textarea::placeholder { color: rgba(120, 120, 120, 0.5); } ================================================ FILE: packages/ui/src/ui-component/dialog/EditVariableDialog.js ================================================ import { createPortal } from 'react-dom' import { useState, useEffect } from 'react' import { useSelector } from 'react-redux' import PropTypes from 'prop-types' import { Button, Dialog, DialogActions, DialogContent, IconButton, Box, List, Accordion, AccordionSummary, Typography, AccordionDetails } from '@mui/material' import { useTheme } from '@mui/material/styles' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import ReactJson from 'react-json-view' import PerfectScrollbar from 'react-perfect-scrollbar' import { IconArrowsMaximize } from '@tabler/icons' import ExpandDataDialog from './ExpandDataDialog' import { StyledButton } from 'ui-component/StyledButton' import { DarkCodeEditor } from 'ui-component/editor/DarkCodeEditor' import { LightCodeEditor } from 'ui-component/editor/LightCodeEditor' import './EditVariableDialog.css' const isPositiveNumeric = (value) => /^\d+$/.test(value) const EditVariableDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const portalElement = document.getElementById('portal') const theme = useTheme() const customization = useSelector((state) => state.customization) const [inputValue, setInputValue] = useState('') const [input, setInput] = useState(null) const [expanded, setExpanded] = useState(false) const [showExpandDialog, setShowExpandDialog] = useState(false) const [expandDialogProps, setExpandDialogProps] = useState({}) const [copiedVariableBody, setCopiedVariableBody] = useState({}) const [languageType, setLanguageType] = useState('') const handleAccordionChange = (nodeLabel) => (event, isExpanded) => { setExpanded(isExpanded ? nodeLabel : false) } const onExpandDialogClicked = (data, node) => { const dialogProp = { title: `Variable Data: ${node.data.label}`, data, node } setExpandDialogProps(dialogProp) setShowExpandDialog(true) } const onMouseUp = (e) => { if (e.target && e.target.selectionEnd && e.target.value) { const cursorPosition = e.target.selectionEnd const textBeforeCursorPosition = e.target.value.substring(0, cursorPosition) const textAfterCursorPosition = e.target.value.substring(cursorPosition, e.target.value.length) const body = { textBeforeCursorPosition, textAfterCursorPosition } setCopiedVariableBody(body) } else { setCopiedVariableBody({}) } } const onClipboardCopy = (e, node) => { const namespaces = e.namespace let returnVariablePath = `${node.id}` for (let i = 0; i < namespaces.length; i += 1) { const namespace = namespaces[i] if (namespace !== 'root') { if (isPositiveNumeric(namespace)) { if (returnVariablePath.endsWith('.')) { returnVariablePath = returnVariablePath.substring(0, returnVariablePath.length - 1) } returnVariablePath += `[${namespace}]` } else { returnVariablePath += namespace } if (i !== namespaces.length - 1) { returnVariablePath += '.' } } } if (copiedVariableBody) { let newInput = '' if (copiedVariableBody.textBeforeCursorPosition === undefined && copiedVariableBody.textAfterCursorPosition === undefined) newInput = `${inputValue}${`{{${returnVariablePath}}}`}` else newInput = `${copiedVariableBody.textBeforeCursorPosition}{{${returnVariablePath}}}${copiedVariableBody.textAfterCursorPosition}` setInputValue(newInput) } } const onSave = (value) => { // ArrayInputParameter if (dialogProps.arrayItemBody) { const updateArrayValues = { ...dialogProps.arrayItemBody.arrayItemValues, [dialogProps.arrayItemBody.arrayItemInput.name]: value } const updateInitialValues = dialogProps.arrayItemBody.initialValues updateInitialValues[dialogProps.arrayItemBody.arrayItemIndex] = updateArrayValues const updateValues = { ...dialogProps.values, [dialogProps.input.name]: updateInitialValues } onConfirm(updateValues) } else { // InputParameter const updateValues = { ...dialogProps.values, [dialogProps.input.name]: value, submit: null } onConfirm(updateValues) } } // Handle Accordian useEffect(() => { if (dialogProps.values && dialogProps.input) { let inputValues = dialogProps.values let input = dialogProps.input if (dialogProps.arrayItemBody) { inputValues = dialogProps.arrayItemBody.arrayItemValues input = dialogProps.arrayItemBody.arrayItemInput } setInput(input) setInputValue(inputValues[input.name].toString() || '') if (input.type === 'json' || input.type === 'string' || input.type === 'number') setLanguageType('json') if (input.type === 'code') setLanguageType('js') } }, [dialogProps]) const component = show ? (
    {input && (input.type === 'json' || input.type === 'string' || input.type === 'number' || input.type === 'code') && (
    Input {customization.isDarkMode ? ( setInputValue(code)} placeholder={input.placeholder} type={languageType} onMouseUp={(e) => onMouseUp(e)} onBlur={(e) => onMouseUp(e)} style={{ fontSize: '0.875rem', minHeight: 'calc(100vh - 220px)', width: '100%' }} /> ) : ( setInputValue(code)} placeholder={input.placeholder} type={languageType} onMouseUp={(e) => onMouseUp(e)} onBlur={(e) => onMouseUp(e)} style={{ fontSize: '0.875rem', minHeight: 'calc(100vh - 220px)', width: '100%' }} /> )}
    )} {!dialogProps.hideVariables && (
    Variables {dialogProps.availableNodesForVariable.length === 0 && (
    No Variables. Try connect to other nodes.
    )} {dialogProps.availableNodesForVariable.length > 0 && ( {dialogProps.availableNodesForVariable.map((node, index) => ( } aria-controls={`${node.data.label}-content`} id={`${node.data.label}-header`} > {node.data.label}
    onClipboardCopy(e, node)} /> onExpandDialogClicked( node.data.outputResponses && node.data.outputResponses.output ? node.data.outputResponses.output : {}, node ) } >
    ))}
    )}
    )} setShowExpandDialog(false)} onCopyClick={(e, node) => { onClipboardCopy(e, node) setShowExpandDialog(false) }} >
    onSave(inputValue)}> {dialogProps.confirmButtonName}
    ) : null return createPortal(component, portalElement) } EditVariableDialog.propTypes = { show: PropTypes.bool, dialogProps: PropTypes.object, onCancel: PropTypes.func, onConfirm: PropTypes.func } export default EditVariableDialog ================================================ FILE: packages/ui/src/ui-component/dialog/ExpandDataDialog.js ================================================ import { createPortal } from 'react-dom' import { useSelector } from 'react-redux' import PropTypes from 'prop-types' import { Dialog, DialogContent, DialogTitle } from '@mui/material' import ReactJson from 'react-json-view' // utils import { copyToClipboard } from 'utils/genericHelper' const ExpandDataDialog = ({ show, dialogProps, onCancel, onCopyClick, enableClipboard }) => { const portalElement = document.getElementById('portal') const customization = useSelector((state) => state.customization) const component = show ? ( {dialogProps.title} {!enableClipboard && ( copyToClipboard(e)} /> )} {enableClipboard && ( onCopyClick(e, dialogProps.node)} /> )} ) : null return createPortal(component, portalElement) } ExpandDataDialog.propTypes = { show: PropTypes.bool, dialogProps: PropTypes.object, onCancel: PropTypes.func, onCopyClick: PropTypes.func, enableClipboard: PropTypes.bool } export default ExpandDataDialog ================================================ FILE: packages/ui/src/ui-component/dialog/HTMLDialog.js ================================================ import { createPortal } from 'react-dom' import { useState } from 'react' import PropTypes from 'prop-types' import { Dialog, DialogContent, DialogTitle, Tabs, Tab, Box } from '@mui/material' function TabPanel(props) { const { children, value, index, ...other } = props return ( ) } TabPanel.propTypes = { children: PropTypes.node, index: PropTypes.number.isRequired, value: PropTypes.number.isRequired } function a11yProps(index) { return { id: `attachment-tab-${index}`, 'aria-controls': `attachment-tabpanel-${index}` } } const HTMLDialog = ({ show, dialogProps, onCancel }) => { const portalElement = document.getElementById('portal') const [value, setValue] = useState(0) const handleChange = (event, newValue) => { setValue(newValue) } const component = show ? ( {dialogProps.title} {dialogProps.executionData.map((execObj, execObjIndex) => ( ))} {dialogProps.executionData.map((execObj, execObjIndex) => ( {execObj.html && (
    )} {!execObj.html &&
    No HTML
    } ))}
    ) : null return createPortal(component, portalElement) } HTMLDialog.propTypes = { show: PropTypes.bool, dialogProps: PropTypes.object, onCancel: PropTypes.func } export default HTMLDialog ================================================ FILE: packages/ui/src/ui-component/dialog/SaveWorkflowDialog.js ================================================ import { createPortal } from 'react-dom' import { useState, useEffect } from 'react' import PropTypes from 'prop-types' import { Button, Dialog, DialogActions, DialogContent, OutlinedInput, DialogTitle } from '@mui/material' import { StyledButton } from 'ui-component/StyledButton' const SaveWorkflowDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const portalElement = document.getElementById('portal') const [workflowName, setWorkflowName] = useState('') const [isReadyToSave, setIsReadyToSave] = useState(false) useEffect(() => { if (workflowName) setIsReadyToSave(true) else setIsReadyToSave(false) }, [workflowName]) const component = show ? ( {dialogProps.title} setWorkflowName(e.target.value)} /> onConfirm(workflowName)}> {dialogProps.confirmButtonName} ) : null return createPortal(component, portalElement) } SaveWorkflowDialog.propTypes = { show: PropTypes.bool, dialogProps: PropTypes.object, onCancel: PropTypes.func, onConfirm: PropTypes.func } export default SaveWorkflowDialog ================================================ FILE: packages/ui/src/ui-component/dialog/TestWorkflowDialog.js ================================================ import { createPortal } from 'react-dom' import PropTypes from 'prop-types' import { useSelector } from 'react-redux' import { useState, useEffect } from 'react' import { Dialog, DialogContent, DialogTitle, Box, Divider, InputAdornment, List, ListItemButton, ListItem, ListItemAvatar, ListItemText, OutlinedInput, Stack } from '@mui/material' import { useTheme } from '@mui/material/styles' // icons import { IconSearch } from '@tabler/icons' // const import { baseURL } from 'store/constant' const TestWorkflowDialog = ({ show, dialogProps, onCancel, onItemClick }) => { const portalElement = document.getElementById('portal') const theme = useTheme() const customization = useSelector((state) => state.customization) const [searchValue, setSearchValue] = useState('') const [nodes, setNodes] = useState([]) const filterSearch = (value) => { setSearchValue(value) setTimeout(() => { if (value) { const returnData = dialogProps.nodes.filter((nd) => nd.data.label.toLowerCase().includes(value.toLowerCase())) setNodes(returnData) } else if (value === '') { setNodes(dialogProps.nodes) } }, 500) } useEffect(() => { if (dialogProps.nodes) { setNodes(dialogProps.nodes) } }, [dialogProps]) const component = show ? ( {dialogProps.title} Select a starting point to test from. Workflow will be executed from the starting point till the end. filterSearch(e.target.value)} placeholder='Search nodes' startAdornment={ } aria-describedby='search-helper-text' inputProps={{ 'aria-label': 'weight' }} />
    {nodes.map((node) => (
    onItemClick(node.id)} >
    {node.data.name}
    ))}
    ) : null return createPortal(component, portalElement) } TestWorkflowDialog.propTypes = { show: PropTypes.bool, dialogProps: PropTypes.object, onCancel: PropTypes.func, onItemClick: PropTypes.func } export default TestWorkflowDialog ================================================ FILE: packages/ui/src/ui-component/editor/DarkCodeEditor.js ================================================ import Editor from 'react-simple-code-editor' import { highlight, languages } from 'prismjs/components/prism-core' import 'prismjs/components/prism-clike' import 'prismjs/components/prism-javascript' import 'prismjs/components/prism-json' import 'prismjs/components/prism-markup' import './prism-dark.css' import PropTypes from 'prop-types' import { useTheme } from '@mui/material/styles' export const DarkCodeEditor = ({ value, placeholder, type, style, onValueChange, onMouseUp, onBlur }) => { const theme = useTheme() return ( highlight(code, type === 'json' ? languages.json : languages.js)} padding={10} onValueChange={onValueChange} onMouseUp={onMouseUp} onBlur={onBlur} style={{ ...style, background: theme.palette.codeEditor.main }} textareaClassName='editor__textarea' /> ) } DarkCodeEditor.propTypes = { value: PropTypes.string, placeholder: PropTypes.string, type: PropTypes.string, style: PropTypes.object, onValueChange: PropTypes.func, onMouseUp: PropTypes.func, onBlur: PropTypes.func } ================================================ FILE: packages/ui/src/ui-component/editor/LightCodeEditor.js ================================================ import Editor from 'react-simple-code-editor' import { highlight, languages } from 'prismjs/components/prism-core' import 'prismjs/components/prism-clike' import 'prismjs/components/prism-javascript' import 'prismjs/components/prism-json' import 'prismjs/components/prism-markup' import './prism-light.css' import PropTypes from 'prop-types' import { useTheme } from '@mui/material/styles' export const LightCodeEditor = ({ value, placeholder, type, style, onValueChange, onMouseUp, onBlur }) => { const theme = useTheme() return ( highlight(code, type === 'json' ? languages.json : languages.js)} padding={10} onValueChange={onValueChange} onMouseUp={onMouseUp} onBlur={onBlur} style={{ ...style, background: theme.palette.card.main }} textareaClassName='editor__textarea' /> ) } LightCodeEditor.propTypes = { value: PropTypes.string, placeholder: PropTypes.string, type: PropTypes.string, style: PropTypes.object, onValueChange: PropTypes.func, onMouseUp: PropTypes.func, onBlur: PropTypes.func } ================================================ FILE: packages/ui/src/ui-component/editor/prism-dark.css ================================================ pre[class*='language-'], code[class*='language-'] { color: #d4d4d4; font-size: 13px; text-shadow: none; font-family: Menlo, Monaco, Consolas, 'Andale Mono', 'Ubuntu Mono', 'Courier New', monospace; direction: ltr; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; line-height: 1.5; -moz-tab-size: 4; -o-tab-size: 4; tab-size: 4; -webkit-hyphens: none; -moz-hyphens: none; -ms-hyphens: none; hyphens: none; } pre[class*='language-']::selection, code[class*='language-']::selection, pre[class*='language-'] *::selection, code[class*='language-'] *::selection { text-shadow: none; background: #264f78; } @media print { pre[class*='language-'], code[class*='language-'] { text-shadow: none; } } pre[class*='language-'] { padding: 1em; margin: 0.5em 0; overflow: auto; background: #1e1e1e; } :not(pre) > code[class*='language-'] { padding: 0.1em 0.3em; border-radius: 0.3em; color: #db4c69; background: #1e1e1e; } /********************************************************* * Tokens */ .namespace { opacity: 0.7; } .token.doctype .token.doctype-tag { color: #569cd6; } .token.doctype .token.name { color: #9cdcfe; } .token.comment, .token.prolog { color: #6a9955; } .token.punctuation, .language-html .language-css .token.punctuation, .language-html .language-javascript .token.punctuation { color: #d4d4d4; } .token.property, .token.tag, .token.boolean, .token.number, .token.constant, .token.symbol, .token.inserted, .token.unit { color: #b5cea8; } .token.selector, .token.attr-name, .token.string, .token.char, .token.builtin, .token.deleted { color: #ce9178; } .language-css .token.string.url { text-decoration: underline; } .token.operator, .token.entity { color: #d4d4d4; } .token.operator.arrow { color: #569cd6; } .token.atrule { color: #ce9178; } .token.atrule .token.rule { color: #c586c0; } .token.atrule .token.url { color: #9cdcfe; } .token.atrule .token.url .token.function { color: #dcdcaa; } .token.atrule .token.url .token.punctuation { color: #d4d4d4; } .token.keyword { color: #569cd6; } .token.keyword.module, .token.keyword.control-flow { color: #c586c0; } .token.function, .token.function .token.maybe-class-name { color: #dcdcaa; } .token.regex { color: #d16969; } .token.important { color: #569cd6; } .token.italic { font-style: italic; } .token.constant { color: #9cdcfe; } .token.class-name, .token.maybe-class-name { color: #4ec9b0; } .token.console { color: #9cdcfe; } .token.parameter { color: #9cdcfe; } .token.interpolation { color: #9cdcfe; } .token.punctuation.interpolation-punctuation { color: #569cd6; } .token.boolean { color: #569cd6; } .token.property, .token.variable, .token.imports .token.maybe-class-name, .token.exports .token.maybe-class-name { color: #9cdcfe; } .token.selector { color: #d7ba7d; } .token.escape { color: #d7ba7d; } .token.tag { color: #569cd6; } .token.tag .token.punctuation { color: #808080; } .token.cdata { color: #808080; } .token.attr-name { color: #9cdcfe; } .token.attr-value, .token.attr-value .token.punctuation { color: #ce9178; } .token.attr-value .token.punctuation.attr-equals { color: #d4d4d4; } .token.entity { color: #569cd6; } .token.namespace { color: #4ec9b0; } /********************************************************* * Language Specific */ pre[class*='language-javascript'], code[class*='language-javascript'], pre[class*='language-jsx'], code[class*='language-jsx'], pre[class*='language-typescript'], code[class*='language-typescript'], pre[class*='language-tsx'], code[class*='language-tsx'] { color: #9cdcfe; } pre[class*='language-css'], code[class*='language-css'] { color: #ce9178; } pre[class*='language-html'], code[class*='language-html'] { color: #d4d4d4; } .language-regex .token.anchor { color: #dcdcaa; } .language-html .token.punctuation { color: #808080; } /********************************************************* * Line highlighting */ pre[class*='language-'] > code[class*='language-'] { position: relative; z-index: 1; } .line-highlight.line-highlight { background: #f7ebc6; box-shadow: inset 5px 0 0 #f7d87c; z-index: 0; } ================================================ FILE: packages/ui/src/ui-component/editor/prism-light.css ================================================ code[class*='language-'], pre[class*='language-'] { text-align: left; white-space: pre; word-spacing: normal; word-break: normal; word-wrap: normal; color: #90a4ae; background: #fafafa; font-family: Roboto Mono, monospace; font-size: 1em; line-height: 1.5em; -moz-tab-size: 4; -o-tab-size: 4; tab-size: 4; -webkit-hyphens: none; -moz-hyphens: none; -ms-hyphens: none; hyphens: none; } code[class*='language-']::-moz-selection, pre[class*='language-']::-moz-selection, code[class*='language-'] ::-moz-selection, pre[class*='language-'] ::-moz-selection { background: #cceae7; color: #263238; } code[class*='language-']::selection, pre[class*='language-']::selection, code[class*='language-'] ::selection, pre[class*='language-'] ::selection { background: #cceae7; color: #263238; } :not(pre) > code[class*='language-'] { white-space: normal; border-radius: 0.2em; padding: 0.1em; } pre[class*='language-'] { overflow: auto; position: relative; margin: 0.5em 0; padding: 1.25em 1em; } .language-css > code, .language-sass > code, .language-scss > code { color: #f76d47; } [class*='language-'] .namespace { opacity: 0.7; } .token.atrule { color: #7c4dff; } .token.attr-name { color: #39adb5; } .token.attr-value { color: #f6a434; } .token.attribute { color: #f6a434; } .token.boolean { color: #7c4dff; } .token.builtin { color: #39adb5; } .token.cdata { color: #39adb5; } .token.char { color: #39adb5; } .token.class { color: #39adb5; } .token.class-name { color: #6182b8; } .token.comment { color: #aabfc9; } .token.constant { color: #7c4dff; } .token.deleted { color: #e53935; } .token.doctype { color: #aabfc9; } .token.entity { color: #e53935; } .token.function { color: #7c4dff; } .token.hexcode { color: #f76d47; } .token.id { color: #7c4dff; font-weight: bold; } .token.important { color: #7c4dff; font-weight: bold; } .token.inserted { color: #39adb5; } .token.keyword { color: #7c4dff; } .token.number { color: #f76d47; } .token.operator { color: #39adb5; } .token.prolog { color: #aabfc9; } .token.property { color: #39adb5; } .token.pseudo-class { color: #f6a434; } .token.pseudo-element { color: #f6a434; } .token.punctuation { color: #39adb5; } .token.regex { color: #6182b8; } .token.selector { color: #e53935; } .token.string { color: #f6a434; } .token.symbol { color: #7c4dff; } .token.tag { color: #e53935; } .token.unit { color: #f76d47; } .token.url { color: #e53935; } .token.variable { color: #e53935; } ================================================ FILE: packages/ui/src/ui-component/extended/AnimateButton.js ================================================ import PropTypes from 'prop-types' import { forwardRef } from 'react' // third-party import { motion, useCycle } from 'framer-motion' // ==============================|| ANIMATION BUTTON ||============================== // const AnimateButton = forwardRef(function AnimateButton({ children, type, direction, offset, scale }, ref) { let offset1 let offset2 switch (direction) { case 'up': case 'left': offset1 = offset offset2 = 0 break case 'right': case 'down': default: offset1 = 0 offset2 = offset break } const [x, cycleX] = useCycle(offset1, offset2) const [y, cycleY] = useCycle(offset1, offset2) switch (type) { case 'rotate': return ( {children} ) case 'slide': if (direction === 'up' || direction === 'down') { return ( cycleY()} onHoverStart={() => cycleY()} > {children} ) } return ( cycleX()} onHoverStart={() => cycleX()}> {children} ) case 'scale': default: if (typeof scale === 'number') { scale = { hover: scale, tap: scale } } return ( {children} ) } }) AnimateButton.propTypes = { children: PropTypes.node, offset: PropTypes.number, type: PropTypes.oneOf(['slide', 'scale', 'rotate']), direction: PropTypes.oneOf(['up', 'down', 'left', 'right']), scale: PropTypes.oneOfType([PropTypes.number, PropTypes.object]) } AnimateButton.defaultProps = { type: 'scale', offset: 10, direction: 'right', scale: { hover: 1, tap: 0.9 } } export default AnimateButton ================================================ FILE: packages/ui/src/ui-component/extended/Avatar.js ================================================ import PropTypes from 'prop-types' // material-ui import { useTheme } from '@mui/material/styles' import MuiAvatar from '@mui/material/Avatar' // ==============================|| AVATAR ||============================== // const Avatar = ({ color, outline, size, sx, ...others }) => { const theme = useTheme() const colorSX = color && !outline && { color: theme.palette.background.paper, bgcolor: `${color}.main` } const outlineSX = outline && { color: color ? `${color}.main` : `primary.main`, bgcolor: theme.palette.background.paper, border: '2px solid', borderColor: color ? `${color}.main` : `primary.main` } let sizeSX = {} switch (size) { case 'badge': sizeSX = { width: theme.spacing(3.5), height: theme.spacing(3.5) } break case 'xs': sizeSX = { width: theme.spacing(4.25), height: theme.spacing(4.25) } break case 'sm': sizeSX = { width: theme.spacing(5), height: theme.spacing(5) } break case 'lg': sizeSX = { width: theme.spacing(9), height: theme.spacing(9) } break case 'xl': sizeSX = { width: theme.spacing(10.25), height: theme.spacing(10.25) } break case 'md': sizeSX = { width: theme.spacing(7.5), height: theme.spacing(7.5) } break default: sizeSX = {} } return } Avatar.propTypes = { className: PropTypes.string, color: PropTypes.string, outline: PropTypes.bool, size: PropTypes.string, sx: PropTypes.object } export default Avatar ================================================ FILE: packages/ui/src/ui-component/extended/Breadcrumbs.js ================================================ import PropTypes from 'prop-types' import { useEffect, useState } from 'react' import { Link } from 'react-router-dom' // material-ui import { useTheme } from '@mui/material/styles' import { Box, Card, Divider, Grid, Typography } from '@mui/material' import MuiBreadcrumbs from '@mui/material/Breadcrumbs' // project imports import config from 'config' import { gridSpacing } from 'store/constant' // assets import { IconTallymark1 } from '@tabler/icons' import AccountTreeTwoToneIcon from '@mui/icons-material/AccountTreeTwoTone' import HomeIcon from '@mui/icons-material/Home' import HomeTwoToneIcon from '@mui/icons-material/HomeTwoTone' const linkSX = { display: 'flex', color: 'grey.900', textDecoration: 'none', alignContent: 'center', alignItems: 'center' } // ==============================|| BREADCRUMBS ||============================== // const Breadcrumbs = ({ card, divider, icon, icons, maxItems, navigation, rightAlign, separator, title, titleBottom, ...others }) => { const theme = useTheme() const iconStyle = { marginRight: theme.spacing(0.75), marginTop: `-${theme.spacing(0.25)}`, width: '1rem', height: '1rem', color: theme.palette.secondary.main } const [main, setMain] = useState() const [item, setItem] = useState() // set active item state const getCollapse = (menu) => { if (menu.children) { menu.children.filter((collapse) => { if (collapse.type && collapse.type === 'collapse') { getCollapse(collapse) } else if (collapse.type && collapse.type === 'item') { if (document.location.pathname === config.basename + collapse.url) { setMain(menu) setItem(collapse) } } return false }) } } useEffect(() => { navigation?.items?.map((menu) => { if (menu.type && menu.type === 'group') { getCollapse(menu) } return false }) }) // item separator const SeparatorIcon = separator const separatorIcon = separator ? : let mainContent let itemContent let breadcrumbContent = let itemTitle = '' let CollapseIcon let ItemIcon // collapse item if (main && main.type === 'collapse') { CollapseIcon = main.icon ? main.icon : AccountTreeTwoToneIcon mainContent = ( {icons && } {main.title} ) } // items if (item && item.type === 'item') { itemTitle = item.title ItemIcon = item.icon ? item.icon : AccountTreeTwoToneIcon itemContent = ( {icons && } {itemTitle} ) // main if (item.breadcrumbs !== false) { breadcrumbContent = ( {title && !titleBottom && ( {item.title} )} {icons && } {icon && } {!icon && 'Dashboard'} {mainContent} {itemContent} {title && titleBottom && ( {item.title} )} {card === false && divider !== false && } ) } } return breadcrumbContent } Breadcrumbs.propTypes = { card: PropTypes.bool, divider: PropTypes.bool, icon: PropTypes.bool, icons: PropTypes.bool, maxItems: PropTypes.number, navigation: PropTypes.object, rightAlign: PropTypes.bool, separator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), title: PropTypes.bool, titleBottom: PropTypes.bool } export default Breadcrumbs ================================================ FILE: packages/ui/src/ui-component/extended/Transitions.js ================================================ import PropTypes from 'prop-types' import { forwardRef } from 'react' // material-ui import { Collapse, Fade, Box, Grow, Slide, Zoom } from '@mui/material' // ==============================|| TRANSITIONS ||============================== // const Transitions = forwardRef(function Transitions({ children, position, type, direction, ...others }, ref) { let positionSX = { transformOrigin: '0 0 0' } switch (position) { case 'top-right': positionSX = { transformOrigin: 'top right' } break case 'top': positionSX = { transformOrigin: 'top' } break case 'bottom-left': positionSX = { transformOrigin: 'bottom left' } break case 'bottom-right': positionSX = { transformOrigin: 'bottom right' } break case 'bottom': positionSX = { transformOrigin: 'bottom' } break case 'top-left': default: positionSX = { transformOrigin: '0 0 0' } break } return ( {type === 'grow' && ( {children} )} {type === 'collapse' && ( {children} )} {type === 'fade' && ( {children} )} {type === 'slide' && ( {children} )} {type === 'zoom' && ( {children} )} ) }) Transitions.propTypes = { children: PropTypes.node, type: PropTypes.oneOf(['grow', 'fade', 'collapse', 'slide', 'zoom']), position: PropTypes.oneOf(['top-left', 'top-right', 'top', 'bottom-left', 'bottom-right', 'bottom']), direction: PropTypes.oneOf(['up', 'down', 'left', 'right']) } Transitions.defaultProps = { type: 'grow', position: 'top-left', direction: 'up' } export default Transitions ================================================ FILE: packages/ui/src/utils/genericHelper.js ================================================ import lodash from 'lodash' import moment from 'moment' export const numberOrExpressionRegex = /^(\d+\.?\d*|{{.*}})$/ //return true if string consists only numbers OR expression {{}} export const constructNodeDirectedGraph = (nodes, edges, reverse = false) => { const graph = {} const nodeDependencies = {} // Initialize node dependencies and graph for (let i = 0; i < nodes.length; i += 1) { const nodeId = nodes[i].id nodeDependencies[nodeId] = 0 graph[nodeId] = [] } for (let i = 0; i < edges.length; i += 1) { const source = edges[i].source const target = edges[i].target if (Object.prototype.hasOwnProperty.call(graph, source)) { graph[source].push(target) } else { graph[source] = [target] } if (reverse) { if (Object.prototype.hasOwnProperty.call(graph, target)) { graph[target].push(source) } else { graph[target] = [source] } } nodeDependencies[target] += 1 } return { graph, nodeDependencies } } // Find starting node with 0 dependencies export const findStartingNodeIds = (nodes, nodeDependencies) => { const startingNodeIds = [] Object.keys(nodeDependencies).forEach((nodeId) => { if (nodeDependencies[nodeId] === 0) { const node = nodes.find((nd) => nd.id === nodeId) if (node && node.data && node.data.type && (node.data.type === 'trigger' || node.data.type === 'webhook')) { startingNodeIds.push(nodeId) } } }) return startingNodeIds } // Backtrack function to find all paths from start to target node export const getAllPathsFromStartToTarget = (startNodeId, targetNodeId, graph) => { const paths = [] const visitedNodeIds = new Set() const DFS = (currentNodeId, endNodeId, tempPath) => { if (currentNodeId === endNodeId) { paths.push(lodash.cloneDeep(tempPath)) return } const neighbourNodeIds = graph[currentNodeId] visitedNodeIds.add(currentNodeId) for (let i = 0; i < neighbourNodeIds.length; i += 1) { const neighNodeId = neighbourNodeIds[i] if (!visitedNodeIds.has(neighNodeId)) { tempPath.push(neighNodeId) DFS(neighNodeId, endNodeId, tempPath) tempPath.pop() } } visitedNodeIds.delete(currentNodeId) } DFS(startNodeId, targetNodeId, [startNodeId]) return paths } // Breadth First Search to get all connected parent nodes from target export const getAllConnectedNodesFromTarget = (targetNodeId, edges, graph) => { const nodeQueue = [] const exploredNodes = [] nodeQueue.push(targetNodeId) exploredNodes.push(targetNodeId) while (nodeQueue.length) { const nodeId = nodeQueue.shift() || '' const parentNodeIds = [] const inputEdges = edges.filter((edg) => edg.target === nodeId && edg.targetHandle.includes('-input-')) if (inputEdges && inputEdges.length) { for (let j = 0; j < inputEdges.length; j += 1) { parentNodeIds.push(inputEdges[j].source) } } const neighbourNodeIds = graph[nodeId] for (let i = 0; i < neighbourNodeIds.length; i += 1) { const neighNodeId = neighbourNodeIds[i] if (parentNodeIds.includes(neighNodeId)) { if (!exploredNodes.includes(neighNodeId)) { exploredNodes.push(neighNodeId) nodeQueue.push(neighNodeId) } } } } return exploredNodes } export const getAvailableNodeIdsForVariable = (nodes, edges, targetNodeId) => { const { graph } = constructNodeDirectedGraph(nodes, edges, true) const exploreNodes = getAllConnectedNodesFromTarget(targetNodeId, edges, graph) const setPath = new Set(exploreNodes) setPath.delete(targetNodeId) return [...setPath] } export const generateWebhookEndpoint = () => { const characters = 'abcdefghijklmnopqrstuvwxyz0123456789' const webhookEndpoint = Array.from({ length: 15 }) .map(() => { return characters.charAt(Math.floor(Math.random() * characters.length)) }) .join('') return webhookEndpoint } export const getUniqueNodeId = (nodeData, nodes) => { // Get amount of same nodes let totalSameNodes = 0 for (let i = 0; i < nodes.length; i += 1) { const node = nodes[i] if (node.data.name === nodeData.name) { totalSameNodes += 1 } } // Get unique id let nodeId = `${nodeData.name}_${totalSameNodes}` for (let i = 0; i < nodes.length; i += 1) { const node = nodes[i] if (node.id === nodeId) { totalSameNodes += 1 nodeId = `${nodeData.name}_${totalSameNodes}` } } return nodeId } const getUniqueNodeLabel = (nodeData, nodes) => { // Get amount of same nodes let totalSameNodes = 0 for (let i = 0; i < nodes.length; i += 1) { const node = nodes[i] if (node.data.name === nodeData.name) { totalSameNodes += 1 } } // Get unique label let nodeLabel = `${nodeData.label}_${totalSameNodes}` for (let i = 0; i < nodes.length; i += 1) { const node = nodes[i] if (node.data.label === nodeLabel) { totalSameNodes += 1 nodeLabel = `${nodeData.label}_${totalSameNodes}` } } return totalSameNodes === 0 ? nodeData.label : nodeLabel } export const checkIfNodeLabelUnique = (nodeLabel, nodes) => { for (let i = 0; i < nodes.length; i += 1) { const node = nodes[i] if (node.data.label === nodeLabel) { return false } } return true } export const initializeNodeData = (nodeParams) => { const initialValues = {} for (let i = 0; i < nodeParams.length; i += 1) { const input = nodeParams[i] // Load from nodeParams default values initialValues[input.name] = input.default || '' // Special case for array, always initialize the item if default is not set if (input.type === 'array' && !input.default) { const newObj = {} for (let j = 0; j < input.array.length; j += 1) { newObj[input.array[j].name] = input.array[j].default || '' } initialValues[input.name] = [newObj] } } initialValues.submit = null return initialValues } export const addAnchors = (nodeData, nodes, newNodeId) => { const incoming = nodeData.incoming || 0 const outgoing = nodeData.outgoing || 0 const inputAnchors = [] for (let i = 0; i < incoming; i += 1) { const newInput = { id: `${newNodeId}-input-${i}` } inputAnchors.push(newInput) } const outputAnchors = [] for (let i = 0; i < outgoing; i += 1) { const newOutput = { id: `${newNodeId}-output-${i}` } outputAnchors.push(newOutput) } nodeData.inputAnchors = inputAnchors nodeData.outputAnchors = outputAnchors nodeData.label = getUniqueNodeLabel(nodeData, nodes) if (nodeData.actions) nodeData.actions = initializeNodeData(nodeData.actions) if (nodeData.credentials) nodeData.credentials = initializeNodeData(nodeData.credentials) if (nodeData.networks) nodeData.networks = initializeNodeData(nodeData.networks) if (nodeData.inputParameters) nodeData.inputParameters = initializeNodeData(nodeData.inputParameters) return nodeData } export const getEdgeLabelName = (source) => { const sourceSplit = source.split('-') if (sourceSplit.length && sourceSplit[0].includes('ifElse')) { const outputAnchorsIndex = sourceSplit[sourceSplit.length - 1] return outputAnchorsIndex === '0' ? 'true' : 'false' } return '' } export const checkMultipleTriggers = (nodes) => { for (let i = 0; i < nodes.length; i += 1) { const node = nodes[i] if (node.data.type === 'webhook' || node.data.type === 'trigger') { return true } } return false } export const convertDateStringToDateObject = (dateString) => { if (dateString === undefined || !dateString) return undefined const date = moment(dateString) if (!date.isValid) return undefined // Sat Sep 24 2022 07:30:14 return new Date(date.year(), date.month(), date.date(), date.hours(), date.minutes()) } export const getFileName = (fileBase64) => { const splitDataURI = fileBase64.split(',') const filename = splitDataURI[splitDataURI.length - 1].split(':')[1] return filename } export const getFolderName = (base64ArrayStr) => { try { const base64Array = JSON.parse(base64ArrayStr) const filenames = [] for (let i = 0; i < base64Array.length; i += 1) { const fileBase64 = base64Array[i] const splitDataURI = fileBase64.split(',') const filename = splitDataURI[splitDataURI.length - 1].split(':')[1] filenames.push(filename) } return filenames.length ? filenames.join(',') : '' } catch (e) { return '' } } export const generateExportFlowData = (flowData) => { const nodes = flowData.nodes const edges = flowData.edges for (let i = 0; i < nodes.length; i += 1) { nodes[i].selected = false const node = nodes[i] const newNodeData = { label: node.data.label, name: node.data.name, type: node.data.type, inputAnchors: node.data.inputAnchors, outputAnchors: node.data.outputAnchors, selected: false } if (node.data.inputParameters) { newNodeData.inputParameters = { ...node.data.inputParameters, submit: null } if (node.data.inputParameters.wallet) delete newNodeData.inputParameters.wallet } if (node.data.actions) { newNodeData.actions = { ...node.data.actions, submit: null } if (node.data.actions.wallet) delete newNodeData.actions.wallet } if (node.data.networks) { newNodeData.networks = { ...node.data.networks, submit: null } if (node.data.networks.wallet) delete newNodeData.networks.wallet } if (node.data.credentials && node.data.credentials.credentialMethod) { newNodeData.credentials = { credentialMethod: node.data.credentials.credentialMethod, submit: null } if (node.data.credentials.wallet) delete newNodeData.credentials.wallet } nodes[i].data = newNodeData } const exportJson = { nodes, edges } return exportJson } const isHideRegisteredCredential = (params, paramsType, nodeFlowData) => { if (!nodeFlowData[paramsType] || !nodeFlowData[paramsType]['credentialMethod']) return undefined let clonedParams = params for (let i = 0; i < clonedParams.length; i += 1) { const input = clonedParams[i] if (input.type === 'options') { const selectedCredentialMethodOption = input.options.find((opt) => opt.name === nodeFlowData[paramsType]['credentialMethod']) if ( selectedCredentialMethodOption && selectedCredentialMethodOption !== undefined && selectedCredentialMethodOption.hideRegisteredCredential ) return true } } return false } export const handleCredentialParams = (nodeParams, paramsType, reorganizedParams, nodeFlowData) => { if ( paramsType === 'credentials' && nodeParams.find((nPrm) => nPrm.name === 'registeredCredential') === undefined && nodeParams.find((nPrm) => nPrm.name === 'credentialMethod') !== undefined && !isHideRegisteredCredential(lodash.cloneDeep(reorganizedParams), paramsType, nodeFlowData) ) { // Add hard-coded registeredCredential params nodeParams.push({ name: 'registeredCredential' }) } else if ( paramsType === 'credentials' && nodeParams.find((nPrm) => nPrm.name === 'registeredCredential') !== undefined && nodeParams.find((nPrm) => nPrm.name === 'credentialMethod') !== undefined && isHideRegisteredCredential(lodash.cloneDeep(reorganizedParams), paramsType, nodeFlowData) ) { // Delete registeredCredential params nodeParams = nodeParams.filter((prm) => prm.name !== 'registeredCredential') } else if (paramsType === 'credentials' && nodeParams.find((nPrm) => nPrm.name === 'credentialMethod') === undefined) { // Delete registeredCredential params nodeParams = nodeParams.filter((prm) => prm.name !== 'registeredCredential') } return nodeParams } export const copyToClipboard = (e) => { const src = e.src if (Array.isArray(src) || typeof src === 'object') { navigator.clipboard.writeText(JSON.stringify(src, null, ' ')) } else { navigator.clipboard.writeText(src) } } ================================================ FILE: packages/ui/src/utils/useNotifier.js ================================================ import React from 'react' import { useDispatch, useSelector } from 'react-redux' import { useSnackbar } from 'notistack' import { removeSnackbar } from 'store/actions' let displayed = [] const useNotifier = () => { const dispatch = useDispatch() const notifier = useSelector((state) => state.notifier) const { notifications } = notifier const { enqueueSnackbar, closeSnackbar } = useSnackbar() const storeDisplayed = (id) => { displayed = [...displayed, id] } const removeDisplayed = (id) => { displayed = [...displayed.filter((key) => id !== key)] } React.useEffect(() => { notifications.forEach(({ key, message, options = {}, dismissed = false }) => { if (dismissed) { // dismiss snackbar using notistack closeSnackbar(key) return } // do nothing if snackbar is already displayed if (displayed.includes(key)) return // display snackbar using notistack enqueueSnackbar(message, { key, ...options, onClose: (event, reason, myKey) => { if (options.onClose) { options.onClose(event, reason, myKey) } }, onExited: (event, myKey) => { // remove this snackbar from redux store dispatch(removeSnackbar(myKey)) removeDisplayed(myKey) } }) // keep track of snackbars that we've displayed storeDisplayed(key) }) }, [notifications, closeSnackbar, enqueueSnackbar, dispatch]) } export default useNotifier ================================================ FILE: packages/ui/src/utils/usePrompt.js ================================================ import { useCallback, useContext, useEffect } from 'react' import { UNSAFE_NavigationContext as NavigationContext } from 'react-router-dom' // https://stackoverflow.com/questions/71572678/react-router-v-6-useprompt-typescript export function useBlocker(blocker, when = true) { const { navigator } = useContext(NavigationContext) useEffect(() => { if (!when) return const unblock = navigator.block((tx) => { const autoUnblockingTx = { ...tx, retry() { unblock() tx.retry() } } blocker(autoUnblockingTx) }) return unblock }, [navigator, blocker, when]) } export function usePrompt(message, when = true) { const blocker = useCallback( (tx) => { if (window.confirm(message)) tx.retry() }, [message] ) useBlocker(blocker, when) } ================================================ FILE: packages/ui/src/views/apikey/APIKeyDialog.js ================================================ import { createPortal } from 'react-dom' import PropTypes from 'prop-types' import { useState, useEffect } from 'react' import { useDispatch } from 'react-redux' import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' import { Box, Typography, Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, IconButton, OutlinedInput } from '@mui/material' import { useTheme } from '@mui/material/styles' import { StyledButton } from 'ui-component/StyledButton' // Icons import { IconX, IconCopy } from '@tabler/icons' // API import apikeyApi from 'api/apikey' // utils import useNotifier from 'utils/useNotifier' const APIKeyDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const portalElement = document.getElementById('portal') const theme = useTheme() const dispatch = useDispatch() // ==============================|| Snackbar ||============================== // useNotifier() const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) const [keyName, setKeyName] = useState('') useEffect(() => { if (dialogProps.type === 'EDIT' && dialogProps.key) { setKeyName(dialogProps.key.keyName) } else if (dialogProps.type === 'ADD') { setKeyName('') } }, [dialogProps]) const addNewKey = async () => { try { const createResp = await apikeyApi.createNewAPI({ keyName }) if (createResp.data) { enqueueSnackbar({ message: 'New API key added', options: { key: new Date().getTime() + Math.random(), variant: 'success', action: (key) => ( ) } }) onConfirm() } } catch (error) { const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` enqueueSnackbar({ message: `Failed to add new API key: ${errorData}`, options: { key: new Date().getTime() + Math.random(), variant: 'error', persist: true, action: (key) => ( ) } }) onCancel() } } const saveKey = async () => { try { const saveResp = await apikeyApi.updateAPI(dialogProps.key.id, { keyName }) if (saveResp.data) { enqueueSnackbar({ message: 'API Key saved', options: { key: new Date().getTime() + Math.random(), variant: 'success', action: (key) => ( ) } }) onConfirm() } } catch (error) { const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` enqueueSnackbar({ message: `Failed to save API key: ${errorData}`, options: { key: new Date().getTime() + Math.random(), variant: 'error', persist: true, action: (key) => ( ) } }) onCancel() } } const component = show ? ( {dialogProps.title} {dialogProps.type === 'EDIT' && ( API Key {dialogProps.key.apiKey} navigator.clipboard.writeText(dialogProps.key.apiKey)} > )} Key Name setKeyName(e.target.value)} /> (dialogProps.type === 'ADD' ? addNewKey() : saveKey())}> {dialogProps.confirmButtonName} ) : null return createPortal(component, portalElement) } APIKeyDialog.propTypes = { show: PropTypes.bool, dialogProps: PropTypes.object, onCancel: PropTypes.func, onConfirm: PropTypes.func } export default APIKeyDialog ================================================ FILE: packages/ui/src/views/apikey/index.js ================================================ import { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' // material-ui import { Button, Box, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, IconButton } from '@mui/material' import { useTheme } from '@mui/material/styles' // project imports import MainCard from 'ui-component/cards/MainCard' import APIKeyDialog from './APIKeyDialog' import { TooltipWithParser } from 'ui-component/TooltipWithParser' import { StyledButton } from 'ui-component/StyledButton' // API import apiKeyApi from 'api/apikey' // Hooks import useApi from 'hooks/useApi' // utils import useNotifier from 'utils/useNotifier' // Icons import { IconTrash, IconEdit, IconCopy, IconX } from '@tabler/icons' import APIEmptySVG from 'assets/images/api_empty.svg' // ==============================|| APIKey ||============================== // const APIKey = () => { const theme = useTheme() const customization = useSelector((state) => state.customization) const dispatch = useDispatch() useNotifier() const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) const [showDialog, setShowDialog] = useState(false) const [dialogProps, setDialogProps] = useState({}) const [apiKeys, setAPIKeys] = useState([]) const getAllAPIKeysApi = useApi(apiKeyApi.getAllAPIKeys) const addNew = () => { const dialogProp = { title: 'Add New API Key', type: 'ADD', cancelButtonName: 'Cancel', confirmButtonName: 'Add' } setDialogProps(dialogProp) setShowDialog(true) } const edit = (key) => { const dialogProp = { title: 'Edit API Key', type: 'EDIT', cancelButtonName: 'Cancel', confirmButtonName: 'Save', key } setDialogProps(dialogProp) setShowDialog(true) } const deleteKey = async (key) => { try { const deleteResp = await apiKeyApi.deleteAPI(key.id) if (deleteResp.data) { enqueueSnackbar({ message: 'API key deleted', options: { key: new Date().getTime() + Math.random(), variant: 'success', action: (key) => ( ) } }) onConfirm() } } catch (error) { const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` enqueueSnackbar({ message: `Failed to delete API key: ${errorData}`, options: { key: new Date().getTime() + Math.random(), variant: 'error', persist: true, action: (key) => ( ) } }) onCancel() } } const onConfirm = () => { setShowDialog(false) getAllAPIKeysApi.request() } useEffect(() => { getAllAPIKeysApi.request() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) useEffect(() => { if (getAllAPIKeysApi.data) { setAPIKeys(getAllAPIKeysApi.data) } }, [getAllAPIKeysApi.data]) return ( <>

    API Keys 

    Add New
    {apiKeys.length <= 0 && ( APIEmptySVG
    No API Keys Yet
    )} {apiKeys.length > 0 && ( Key Name API Key Created {apiKeys.map((key, index) => ( {key.keyName} {key.apiKey} navigator.clipboard.writeText(key.apiKey)} > {key.createdAt} edit(key)}> deleteKey(key)}> ))}
    )}
    setShowDialog(false)} onConfirm={onConfirm} > ) } export default APIKey ================================================ FILE: packages/ui/src/views/canvas/AddNodes.js ================================================ import { useState, useRef, useEffect } from 'react' import { useSelector } from 'react-redux' import PropTypes from 'prop-types' // material-ui import { useTheme } from '@mui/material/styles' import { Accordion, AccordionSummary, AccordionDetails, Box, ClickAwayListener, Divider, InputAdornment, List, ListItemButton, ListItem, ListItemAvatar, ListItemText, OutlinedInput, Paper, Popper, Stack, Typography, Tabs, Tab } from '@mui/material' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' // third-party import PerfectScrollbar from 'react-perfect-scrollbar' // project imports import MainCard from 'ui-component/cards/MainCard' import Transitions from 'ui-component/extended/Transitions' import { StyledFab } from 'ui-component/StyledFab' // icons import { IconPlus, IconSearch, IconMinus } from '@tabler/icons' // const import { baseURL } from 'store/constant' // ==============================|| ADD NODES||============================== // function TabPanel(props) { const { children, value, index, ...other } = props return ( ) } TabPanel.propTypes = { children: PropTypes.node, index: PropTypes.number.isRequired, value: PropTypes.number.isRequired } function a11yProps(index) { return { id: `nodes-tab-${index}`, 'aria-controls': `nodes-tabpanel-${index}` } } const AddNodes = ({ nodesData, node }) => { const theme = useTheme() const customization = useSelector((state) => state.customization) const [searchValue, setSearchValue] = useState('') const [nodes, setNodes] = useState({}) const [open, setOpen] = useState(false) const [tabValue, setTabValue] = useState(0) const [categoryExpanded, setCategoryExpanded] = useState({}) const anchorRef = useRef(null) const prevOpen = useRef(open) const ps = useRef() const scrollTop = () => { const curr = ps.current if (curr) { curr.scrollTop = 0 } } const handleTabChange = (event, newValue) => { setTabValue(newValue) let returnData = [] switch (newValue) { case 0: returnData = nodesData break case 1: returnData = nodesData.filter((nd) => nd.type.toLowerCase() === 'trigger') break case 2: returnData = nodesData.filter((nd) => nd.type.toLowerCase() === 'webhook') break case 3: returnData = nodesData.filter((nd) => nd.type.toLowerCase() === 'action') break } groupByCategory(returnData) scrollTop() } const filterSearch = (value) => { setSearchValue(value) setTimeout(() => { if (value) { const returnData = nodesData.filter((nd) => nd.name.toLowerCase().includes(value.toLowerCase())) groupByCategory(returnData, true) setTabValue(0) scrollTop() } else if (value === '') { groupByCategory(nodesData) scrollTop() } }, 500) } const groupByCategory = (nodes, isFilter) => { const accordianCategories = {} const result = nodes.reduce(function (r, a) { r[a.category] = r[a.category] || [] r[a.category].push(a) accordianCategories[a.category] = isFilter ? true : false return r }, Object.create(null)) setNodes(result) setCategoryExpanded(accordianCategories) } const handleAccordionChange = (category) => (event, isExpanded) => { const accordianCategories = { ...categoryExpanded } accordianCategories[category] = isExpanded setCategoryExpanded(accordianCategories) } const handleClose = (event) => { if (anchorRef.current && anchorRef.current.contains(event.target)) { return } setOpen(false) } const handleToggle = () => { setOpen((prevOpen) => !prevOpen) } const onDragStart = (event, node) => { event.dataTransfer.setData('application/reactflow', JSON.stringify(node)) event.dataTransfer.effectAllowed = 'move' } useEffect(() => { if (prevOpen.current === true && open === false) { anchorRef.current.focus() } prevOpen.current = open }, [open]) useEffect(() => { if (node) setOpen(false) }, [node]) useEffect(() => { if (nodesData) groupByCategory(nodesData) }, [nodesData]) return ( <> {open ? : } {({ TransitionProps }) => ( Add Nodes filterSearch(e.target.value)} placeholder='Search nodes' startAdornment={ } aria-describedby='search-helper-text' inputProps={{ 'aria-label': 'weight' }} /> { ps.current = el }} style={{ height: '100%', maxHeight: 'calc(100vh - 375px)', overflowX: 'hidden' }} > {Object.keys(nodes) .sort() .map((category) => ( } aria-controls={`nodes-accordian-${category}`} id={`nodes-accordian-header-${category}`} > {category} {nodes[category].map((node, index) => (
    onDragStart(event, node)} draggable >
    {node.name}
    {index === nodes[category].length - 1 ? null : }
    ))}
    ))}
    )}
    ) } AddNodes.propTypes = { nodesData: PropTypes.array, node: PropTypes.object } export default AddNodes ================================================ FILE: packages/ui/src/views/canvas/ButtonEdge.js ================================================ import { getBezierPath, EdgeText } from 'reactflow' import PropTypes from 'prop-types' import { useDispatch } from 'react-redux' import { REMOVE_EDGE } from 'store/actions' import './index.css' const foreignObjectSize = 40 const ButtonEdge = ({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, style = {}, data, markerEnd }) => { const [edgePath, edgeCenterX, edgeCenterY] = getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition }) const dispatch = useDispatch() const onEdgeClick = (evt, id) => { evt.stopPropagation() dispatch({ type: REMOVE_EDGE, edgeId: `${id}:${Date.now()}` }) } return ( <> {data && data.label && ( )}
    ) } ButtonEdge.propTypes = { id: PropTypes.string, sourceX: PropTypes.number, sourceY: PropTypes.number, targetX: PropTypes.number, targetY: PropTypes.number, sourcePosition: PropTypes.any, targetPosition: PropTypes.any, style: PropTypes.object, data: PropTypes.object, markerEnd: PropTypes.any } export default ButtonEdge ================================================ FILE: packages/ui/src/views/canvas/CanvasHeader.js ================================================ import PropTypes from 'prop-types' import { useNavigate } from 'react-router-dom' import { useSelector } from 'react-redux' import { useEffect, useRef, useState } from 'react' // material-ui import { useTheme } from '@mui/material/styles' import { Avatar, Box, ButtonBase, Typography, Stack, TextField, Chip } from '@mui/material' // icons import { IconSettings, IconChevronLeft, IconDeviceFloppy, IconRocket, IconPencil, IconCheck, IconX, IconPlayerPause, IconListCheck } from '@tabler/icons' // project imports import Executions from 'views/executions' import Settings from 'views/settings' import SaveWorkflowDialog from 'ui-component/dialog/SaveWorkflowDialog' // API import workflowsApi from 'api/workflows' // Hooks import useApi from 'hooks/useApi' // utils import { generateExportFlowData } from 'utils/genericHelper' // ==============================|| CANVAS HEADER ||============================== // const CanvasHeader = ({ workflow, handleSaveFlow, handleDeployWorkflow, handleStopWorkflow, handleDeleteWorkflow, handleLoadWorkflow }) => { const theme = useTheme() const navigate = useNavigate() const workflowNameRef = useRef() const viewExecutionRef = useRef() const settingsRef = useRef() const [isEditingWorkflowName, setEditingWorkflowName] = useState(null) const [workflowName, setWorkflowName] = useState('') const [isExecutionOpen, setExecutionOpen] = useState(false) const [isSettingsOpen, setSettingsOpen] = useState(false) const [workfowDialogOpen, setWorkfowDialogOpen] = useState(false) const updateWorkflowApi = useApi(workflowsApi.updateWorkflow) const canvas = useSelector((state) => state.canvas) const onSettingsItemClick = (setting) => { setSettingsOpen(false) if (setting === 'deleteWorkflow') { handleDeleteWorkflow() } else if (setting === 'exportWorkflow') { try { const flowData = JSON.parse(workflow.flowData) let dataStr = JSON.stringify(generateExportFlowData(flowData)) let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr) let exportFileDefaultName = `${workflow.name} Workflow.json` let linkElement = document.createElement('a') linkElement.setAttribute('href', dataUri) linkElement.setAttribute('download', exportFileDefaultName) linkElement.click() } catch (e) { console.error(e) } } } const onUploadFile = (file) => { setSettingsOpen(false) handleLoadWorkflow(file) } const submitWorkflowName = () => { if (workflow.shortId) { const updateBody = { name: workflowNameRef.current.value } updateWorkflowApi.request(workflow.shortId, updateBody) } } const onSaveWorkflowClick = () => { if (workflow.shortId) handleSaveFlow(workflow.name) else setWorkfowDialogOpen(true) } const onConfirmSaveName = (workflowName) => { setWorkfowDialogOpen(false) handleSaveFlow(workflowName) } useEffect(() => { if (updateWorkflowApi.data) { setWorkflowName(updateWorkflowApi.data.name) } setEditingWorkflowName(false) // eslint-disable-next-line react-hooks/exhaustive-deps }, [updateWorkflowApi.data]) useEffect(() => { if (workflow) { setWorkflowName(workflow.name) } }, [workflow]) return ( <> navigate(-1)} > {!isEditingWorkflowName && ( {canvas.isDirty && *} {workflowName} {workflow?.shortId && ( setEditingWorkflowName(true)} > )} {workflow?.deployed && ( )} )} {isEditingWorkflowName && ( setEditingWorkflowName(false)} > )} {workflow?.shortId && ( setExecutionOpen(!isExecutionOpen)} >
    {workflow?.executionCount}
     
    )} {workflow?.shortId && ( {workflow?.deployed ? ( ) : ( )} )} setSettingsOpen(!isSettingsOpen)} >
    {workflow?.shortId && ( )} setWorkfowDialogOpen(false)} onConfirm={onConfirmSaveName} /> ) } CanvasHeader.propTypes = { workflow: PropTypes.object, handleSaveFlow: PropTypes.func, handleDeployWorkflow: PropTypes.func, handleStopWorkflow: PropTypes.func, handleDeleteWorkflow: PropTypes.func, handleLoadWorkflow: PropTypes.func } export default CanvasHeader ================================================ FILE: packages/ui/src/views/canvas/CanvasNode.js ================================================ import PropTypes from 'prop-types' import { Handle, Position } from 'reactflow' import { useSelector } from 'react-redux' // material-ui import { styled, useTheme } from '@mui/material/styles' import { Avatar, Box, Typography } from '@mui/material' // project imports import MainCard from 'ui-component/cards/MainCard' // icons import { IconCheck, IconExclamationMark } from '@tabler/icons' // const import { baseURL } from 'store/constant' const CardWrapper = styled(MainCard)(({ theme }) => ({ background: theme.palette.card.main, color: theme.darkTextPrimary, border: 'solid 1px', width: '200px', height: 'auto', padding: '10px', boxShadow: '0 2px 14px 0 rgb(32 40 45 / 8%)', '&:hover': { background: theme.palette.card.hover, borderColor: theme.palette.primary.main } })) const handlerPosition = [[['50%']], [['30%'], ['70%']]] // ===========================|| CANVAS NODE ||=========================== // const CanvasNode = ({ data }) => { const theme = useTheme() const customization = useSelector((state) => state.customization) return ( <> {data && data.outputResponses && data.outputResponses.submit && ( )} {data && data.outputResponses && data.outputResponses.needRetest && ( )} {data.inputAnchors.map((inputAnchor, index) => ( ))}
    Notification
    {data.label}
    {data.outputAnchors.map((outputAnchor, index) => ( ))}
    ) } CanvasNode.propTypes = { data: PropTypes.object } export default CanvasNode ================================================ FILE: packages/ui/src/views/canvas/EditNodes.js ================================================ import { useState, useRef, useEffect } from 'react' import PropTypes from 'prop-types' // material-ui import { useTheme } from '@mui/material/styles' import { Accordion, AccordionSummary, AccordionDetails, Box, ClickAwayListener, Divider, Paper, Stack, Popper, Typography, TextField, Avatar } from '@mui/material' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' // third-party import PerfectScrollbar from 'react-perfect-scrollbar' import * as Yup from 'yup' import lodash from 'lodash' // project imports import MainCard from 'ui-component/cards/MainCard' import Transitions from 'ui-component/extended/Transitions' import InputParameters from 'views/inputs/InputParameters' import CredentialInput from 'views/inputs/CredentialInput' import OutputResponses from 'views/output/OutputResponses' import VariableSelector from './VariableSelector' import EditVariableDialog from 'ui-component/dialog/EditVariableDialog' import { StyledFab } from 'ui-component/StyledFab' // API import nodesApi from 'api/nodes' // Hooks import useApi from 'hooks/useApi' // icons import { IconPencil, IconMinus, IconCheck } from '@tabler/icons' // utils import { getAvailableNodeIdsForVariable, numberOrExpressionRegex, handleCredentialParams } from 'utils/genericHelper' // ==============================|| EDIT NODES||============================== // const EditNodes = ({ node, nodes, edges, workflow, onNodeLabelUpdate, onNodeValuesUpdate }) => { const theme = useTheme() const [nodeFlowData, setNodeFlowData] = useState(null) const [nodeLabel, setNodeLabel] = useState('') const [expanded, setExpanded] = useState(false) const [open, setOpen] = useState(false) const [nodeDetails, setNodeDetails] = useState(null) const [nodeParams, setNodeParams] = useState([]) const [nodeParamsType, setNodeParamsType] = useState([]) const [nodeParamsInitialValues, setNodeParamsInitialValues] = useState({}) const [nodeParamsValidation, setNodeParamsValidation] = useState({}) const [isVariableSelectorOpen, setVariableSelectorOpen] = useState(false) const [variableBody, setVariableBody] = useState({}) const [availableNodesForVariable, setAvailableNodesForVariable] = useState(null) const [isEditVariableDialogOpen, setEditVariableDialog] = useState(false) const [editVariableDialogProps, setEditVariableDialogProps] = useState({}) const anchorRef = useRef(null) const ps = useRef() const getSpecificNodeApi = useApi(nodesApi.getSpecificNode) const scrollTop = () => { const curr = ps.current if (curr) { curr.scrollTop = 0 } } const handleClose = (event) => { if (anchorRef.current && anchorRef.current.contains(event.target)) { return } setOpen(false) setVariableSelectorOpen(false) } const handleToggle = () => { setOpen((prevOpen) => !prevOpen) if (open) setVariableSelectorOpen(false) } const handleAccordionChange = (paramsType) => (event, isExpanded) => { setExpanded(isExpanded ? paramsType : false) scrollTop() } const handleNodeLabelChange = (event) => { setNodeLabel(event.target.value) } const saveNodeLabel = () => { onNodeLabelUpdate(nodeLabel) } const onEditVariableDialogOpen = (input, values, arrayItemBody) => { const variableNodesIds = getAvailableNodeIdsForVariable(nodes, edges, node.id) const nodesForVariable = [] for (let i = 0; i < variableNodesIds.length; i += 1) { const nodeId = variableNodesIds[i] const node = nodes.find((nd) => nd.id === nodeId) nodesForVariable.push(node) } const dialogProps = { input, values, arrayItemBody, availableNodesForVariable: nodesForVariable, cancelButtonName: 'Cancel', confirmButtonName: 'Save' } setEditVariableDialogProps(dialogProps) setEditVariableDialog(true) } const setVariableSelectorState = (variableSelectorState, body) => { setVariableSelectorOpen(variableSelectorState) if (body) { setVariableBody(body) const variableNodesIds = getAvailableNodeIdsForVariable(nodes, edges, node.id) const nodesForVariable = [] for (let i = 0; i < variableNodesIds.length; i += 1) { const nodeId = variableNodesIds[i] const node = nodes.find((nd) => nd.id === nodeId) nodesForVariable.push(node) } setAvailableNodesForVariable(nodesForVariable) } } const paramsChanged = (formParams, paramsType) => { // Because formParams options can be changed due to show hide options, // To avoid that, replace with original details options const credentialMethodParam = formParams.find((param) => param.name === 'credentialMethod') const credentialMethodParamIndex = formParams.findIndex((param) => param.name === 'credentialMethod') if (credentialMethodParam !== undefined) { const originalParam = nodeDetails[paramsType].find((param) => param.name === 'credentialMethod') if (originalParam !== undefined) { formParams[credentialMethodParamIndex]['options'] = originalParam.options } } const updateNodeDetails = { ...nodeDetails, [paramsType]: formParams } setNodeDetails(updateNodeDetails) } const valueChanged = (formValues, paramsType) => { const updateNodeFlowData = { ...nodeFlowData, [paramsType]: formValues } // If input parameters change, notify output has to be retest if (nodeFlowData.outputResponses) { const outputResponsesFlowData = nodeFlowData.outputResponses outputResponsesFlowData.submit = null outputResponsesFlowData.needRetest = true updateNodeFlowData.outputResponses = outputResponsesFlowData } setNodeFlowData(updateNodeFlowData) onNodeValuesUpdate(updateNodeFlowData) } const onVariableSelected = (returnVariablePath) => { if (variableBody) { const path = variableBody.path const paramsType = variableBody.paramsType const newInput = `${variableBody.textBeforeCursorPosition}{{${returnVariablePath}}}${variableBody.textAfterCursorPosition}` const clonedNodeFlowData = lodash.cloneDeep(nodeFlowData) lodash.set(clonedNodeFlowData, path, newInput) valueChanged(clonedNodeFlowData[paramsType], paramsType) } } const onSubmit = (formValues, paramsType) => { const updateNodeFlowData = { ...nodeFlowData, [paramsType]: formValues } setNodeFlowData(updateNodeFlowData) onNodeValuesUpdate(updateNodeFlowData) const index = nodeParamsType.indexOf(paramsType) if (index >= 0 && index !== nodeParamsType.length - 1) { setExpanded(nodeParamsType[index + 1]) scrollTop() } } const showHideParameters = (input, displayType, index, toBeDeleteParams) => { const displayOptions = input[displayType] Object.keys(displayOptions).forEach((path) => { const comparisonValue = displayOptions[path] if (path.includes('$index')) { path = path.replace('$index', index) } const groundValue = lodash.get(nodeFlowData, path, '') if (Array.isArray(comparisonValue)) { if (displayType === 'show' && !comparisonValue.includes(groundValue)) { toBeDeleteParams.push(input) } if (displayType === 'hide' && comparisonValue.includes(groundValue)) { toBeDeleteParams.push(input) } } else if (typeof comparisonValue === 'string') { if (displayType === 'show' && !(comparisonValue === groundValue || new RegExp(comparisonValue).test(groundValue))) { toBeDeleteParams.push(input) } if (displayType === 'hide' && (comparisonValue === groundValue || new RegExp(comparisonValue).test(groundValue))) { toBeDeleteParams.push(input) } } }) } const displayParameters = (params, paramsType, arrayIndex) => { const toBeDeleteParams = [] for (let i = 0; i < params.length; i += 1) { const input = params[i] if (input.type === 'array') { const arrayInitialValue = lodash.get(nodeFlowData, `${paramsType}.${input.name}`, []) const inputArray = [] for (let j = arrayIndex; j < arrayInitialValue.length; j += 1) { inputArray.push(displayParameters(input.array || [], paramsType, j)) } input.arrayParams = inputArray } if (input.show) { showHideParameters(input, 'show', arrayIndex, toBeDeleteParams) } if (input.hide) { showHideParameters(input, 'hide', arrayIndex, toBeDeleteParams) } } let returnParams = params for (let i = 0; i < toBeDeleteParams.length; i += 1) { returnParams = returnParams.filter((prm) => JSON.stringify(prm) !== JSON.stringify(toBeDeleteParams[i])) } return returnParams } const showHideOptions = (displayType, index, options) => { let returnOptions = options const toBeDeleteOptions = [] for (let i = 0; i < returnOptions.length; i += 1) { const option = returnOptions[i] const displayOptions = option[displayType] if (displayOptions) { Object.keys(displayOptions).forEach((path) => { const comparisonValue = displayOptions[path] if (path.includes('$index')) { path = path.replace('$index', index) } const groundValue = lodash.get(nodeFlowData, path, '') if (Array.isArray(comparisonValue)) { if (displayType === 'show' && !comparisonValue.includes(groundValue)) { toBeDeleteOptions.push(option) } if (displayType === 'hide' && comparisonValue.includes(groundValue)) { toBeDeleteOptions.push(option) } } else if (typeof comparisonValue === 'string') { if (displayType === 'show' && !(comparisonValue === groundValue || new RegExp(comparisonValue).test(groundValue))) { toBeDeleteOptions.push(option) } if (displayType === 'hide' && (comparisonValue === groundValue || new RegExp(comparisonValue).test(groundValue))) { toBeDeleteOptions.push(option) } } }) } } for (let i = 0; i < toBeDeleteOptions.length; i += 1) { returnOptions = returnOptions.filter((opt) => JSON.stringify(opt) !== JSON.stringify(toBeDeleteOptions[i])) } return returnOptions } const displayOptions = (params, paramsType, arrayIndex) => { let clonedParams = params for (let i = 0; i < clonedParams.length; i += 1) { const input = clonedParams[i] if (input.type === 'array') { const arrayInitialValue = lodash.get(nodeFlowData, `${paramsType}.${input.name}`, []) const inputArray = [] for (let j = arrayIndex; j < arrayInitialValue.length; j += 1) { inputArray.push(displayOptions(input.arrayParams[j] || [], paramsType, j)) } input.arrayParams = inputArray } if (input.type === 'options') { input.options = showHideOptions('show', arrayIndex, input.options) input.options = showHideOptions('hide', arrayIndex, input.options) } } return clonedParams } const setYupValidation = (params) => { const validationSchema = {} for (let i = 0; i < params.length; i += 1) { const input = params[i] let inputOptional = input.optional if (typeof input.optional === 'object' && input.optional !== null) { const keys = Object.keys(input.optional) inputOptional = true for (let j = 0; j < keys.length; j += 1) { const path = keys[j] const comparisonValue = input.optional[path] const groundValue = lodash.get(nodeFlowData, path, '') if (Array.isArray(comparisonValue)) { inputOptional = inputOptional && comparisonValue.includes(groundValue) } else if (typeof comparisonValue === 'string') { inputOptional = inputOptional && (comparisonValue === groundValue || new RegExp(comparisonValue).test(groundValue)) } } } if ( (input.type === 'string' || input.type === 'password' || input.type === 'date' || input.type === 'code' || input.type === 'json' || input.type === 'file' || input.type === 'options' || input.type === 'asyncOptions') && !inputOptional ) { validationSchema[input.name] = Yup.string().required(`${input.label} is required. Type: ${input.type}`) } else if (input.type === 'number' && !inputOptional) { validationSchema[input.name] = Yup.string() .required(`${input.label} is required. Type: ${input.type}`) .matches(numberOrExpressionRegex, `${input.label} must be numbers or a variable expression.`) } else if (input.type === 'array' && !inputOptional) { /* ************ * Limitation on different object shape within array: https://github.com/jquense/yup/issues/757 ************ const innerValidationSchema = setYupValidation(input.arrayParams); validationSchema[input.name] = Yup.array(Yup.object(innerValidationSchema)).required(`Must have ${input.label}`).min(1, `Minimum of 1 ${input.label}`); */ } } return validationSchema } const initializeFormValuesAndParams = (paramsType) => { const initialValues = {} const reorganizedParams = displayParameters(nodeDetails[paramsType] || [], paramsType, 0) let nodeParams = displayOptions(lodash.cloneDeep(reorganizedParams), paramsType, 0) nodeParams = handleCredentialParams(nodeParams, paramsType, reorganizedParams, nodeFlowData) for (let i = 0; i < nodeParams.length; i += 1) { const input = nodeParams[i] // Load from nodeFlowData values if (paramsType in nodeFlowData && input.name in nodeFlowData[paramsType]) { initialValues[input.name] = nodeFlowData[paramsType][input.name] // Check if option value is still available from the list of options if (input.type === 'options') { const optionVal = input.options.find((option) => option.name === initialValues[input.name]) if (!optionVal) delete initialValues[input.name] } } else { // Load from nodeParams default values initialValues[input.name] = input.default || '' /** * Special case for array, always initialize the item if default is not set * Disabling for now if (input.type === 'array' && !input.default) { const newObj = {} for (let j = 0; j < input.array.length; j += 1) { newObj[input.array[j].name] = input.array[j].default || '' } initialValues[input.name] = [newObj] } */ } } initialValues.submit = null setNodeParamsInitialValues(initialValues) setNodeParamsValidation(setYupValidation(nodeParams)) setNodeParams(nodeParams) } // Handle Accordian const prevOpen = useRef(open) useEffect(() => { if (prevOpen.current === true && open === false) { anchorRef.current.focus() } prevOpen.current = open }, [open]) // Get Node Details from API useEffect(() => { if (getSpecificNodeApi.data) { const nodeDetails = getSpecificNodeApi.data setNodeDetails(nodeDetails) const nodeParamsType = [] if (nodeDetails.actions) nodeParamsType.push('actions') if (nodeDetails.networks) nodeParamsType.push('networks') if (nodeDetails.credentials) nodeParamsType.push('credentials') if (nodeDetails.inputParameters) nodeParamsType.push('inputParameters') nodeParamsType.push('outputResponses') setNodeParamsType(nodeParamsType) if (nodeParamsType.length) { setExpanded(nodeParamsType[0]) scrollTop() } } }, [getSpecificNodeApi.data]) // Initialization useEffect(() => { if (node) { setOpen(true) setNodeLabel(node.data.label) setNodeFlowData(node.data) getSpecificNodeApi.request(node.data.name) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [node]) // Initialize Parameters Initial Values & Validation useEffect(() => { if (nodeDetails && nodeFlowData && expanded) { initializeFormValuesAndParams(expanded) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [nodeDetails, nodeFlowData, expanded]) return ( <> {open ? : } {({ TransitionProps }) => ( Edit Nodes { ps.current = el }} style={{ height: '100%', maxHeight: 'calc(100vh - 250px)', overflowX: 'hidden' }} > {!node && No data} {nodeFlowData && nodeFlowData.label && ( )} {/* actions */} {nodeParamsType.includes('actions') && ( } aria-controls='actions-content' id='actions-header' > Actions {nodeFlowData && nodeFlowData.actions && nodeFlowData.actions.submit && ( )} )} {/* networks */} {nodeParamsType.includes('networks') && ( } aria-controls='networks-content' id='networks-header' > Networks {nodeFlowData && nodeFlowData.networks && nodeFlowData.networks.submit && ( )} )} {/* credentials */} {nodeParamsType.includes('credentials') && ( } aria-controls='credentials-content' id='credentials-header' > Credentials {nodeFlowData && nodeFlowData.credentials && nodeFlowData.credentials.submit && ( )} )} {/* inputParameters */} {nodeParamsType.includes('inputParameters') && ( } aria-controls='inputParameters-content' id='inputParameters-header' > Input Parameters {nodeFlowData && nodeFlowData.inputParameters && nodeFlowData.inputParameters.submit && ( )} )} {/* outputResponses */} {nodeDetails && nodeFlowData && ( } aria-controls='outputResponses-content' id='outputResponses-header' > Output Responses {nodeFlowData && nodeFlowData.outputResponses && nodeFlowData.outputResponses.submit && ( )} )} onVariableSelected(returnVariablePath)} handleClose={() => setVariableSelectorOpen(false)} /> setEditVariableDialog(false)} onConfirm={(updateValues) => { valueChanged(updateValues, expanded) setEditVariableDialog(false) }} /> )} ) } EditNodes.propTypes = { node: PropTypes.object, nodes: PropTypes.array, edges: PropTypes.array, workflow: PropTypes.object, onNodeLabelUpdate: PropTypes.func, onNodeValuesUpdate: PropTypes.func } export default EditNodes ================================================ FILE: packages/ui/src/views/canvas/VariableSelector.js ================================================ import { useState, useRef, useEffect } from 'react' import { useSelector } from 'react-redux' import PropTypes from 'prop-types' // material-ui import { useTheme } from '@mui/material/styles' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import { Box, List, Accordion, AccordionSummary, AccordionDetails, Paper, Popper, Stack, Typography, IconButton } from '@mui/material' // third-party import PerfectScrollbar from 'react-perfect-scrollbar' import ReactJson from 'react-json-view' // project imports import MainCard from 'ui-component/cards/MainCard' import Transitions from 'ui-component/extended/Transitions' import ExpandDataDialog from 'ui-component/dialog/ExpandDataDialog' import { StyledFab } from 'ui-component/StyledFab' // icons import { IconX, IconArrowsMaximize } from '@tabler/icons' // ==============================|| VARIABLE SELECTOR ||============================== // const isPositiveNumeric = (value) => /^\d+$/.test(value) const VariableSelector = ({ nodes, isVariableSelectorOpen, anchorEl, onVariableSelected, handleClose }) => { const theme = useTheme() const customization = useSelector((state) => state.customization) const [expanded, setExpanded] = useState(false) const [open, setOpen] = useState(false) const [showExpandDialog, setShowExpandDialog] = useState(false) const [expandDialogProps, setExpandDialogProps] = useState({}) const varPrevOpen = useRef(open) const handleAccordionChange = (nodeLabel) => (event, isExpanded) => { setExpanded(isExpanded ? nodeLabel : false) } const onClipboardCopy = (e, node) => { const namespaces = e.namespace let returnVariablePath = `${node.id}` for (let i = 0; i < namespaces.length; i += 1) { const namespace = namespaces[i] if (namespace !== 'root') { if (isPositiveNumeric(namespace)) { if (returnVariablePath.endsWith('.')) { returnVariablePath = returnVariablePath.substring(0, returnVariablePath.length - 1) } returnVariablePath += `[${namespace}]` } else { returnVariablePath += namespace } if (i !== namespaces.length - 1) { returnVariablePath += '.' } } } onVariableSelected(returnVariablePath) } const onExpandDialogClicked = (data, node) => { const dialogProp = { title: `Variable Data: ${node.data.label}`, data, node } setExpandDialogProps(dialogProp) setShowExpandDialog(true) } // Handle Accordian useEffect(() => { varPrevOpen.current = open }, [open]) useEffect(() => { setOpen(isVariableSelectorOpen) }, [isVariableSelectorOpen]) return ( <> {({ TransitionProps }) => ( Variable Selector {nodes && nodes.length === 0 && No variables} {nodes && nodes.length > 0 && ( {nodes.map((node, index) => ( } aria-controls={`${node.data.label}-content`} id={`${node.data.label}-header`} > {node.data.label}
    onClipboardCopy(e, node)} /> onExpandDialogClicked( node.data.outputResponses && node.data.outputResponses.output ? node.data.outputResponses.output : {}, node ) } >
    ))}
    )}
    )}
    setShowExpandDialog(false)} onCopyClick={(e, node) => { onClipboardCopy(e, node) setShowExpandDialog(false) }} > ) } VariableSelector.propTypes = { nodes: PropTypes.array, isVariableSelectorOpen: PropTypes.bool, anchorEl: PropTypes.any, onVariableSelected: PropTypes.func, handleClose: PropTypes.func } export default VariableSelector ================================================ FILE: packages/ui/src/views/canvas/index.css ================================================ .edgebutton { width: 20px; height: 20px; background: #eee; border: 1px solid #fff; cursor: pointer; border-radius: 50%; font-size: 12px; line-height: 1; } .edgebutton:hover { background: #5e35b1; color: #eee; box-shadow: 0 0 6px 2px rgba(0, 0, 0, 0.08); } .edgebutton-foreignobject div { background: transparent; width: 40px; height: 40px; display: flex; justify-content: center; align-items: center; min-height: 40px; } .reactflow-parent-wrapper { display: flex; flex-grow: 1; height: 100%; } .reactflow-parent-wrapper .reactflow-wrapper { flex-grow: 1; height: 100%; } ================================================ FILE: packages/ui/src/views/canvas/index.js ================================================ import { useEffect, useRef, useState, useCallback } from 'react' import ReactFlow, { addEdge, MiniMap, Controls, Background, useNodesState, useEdgesState } from 'reactflow' import 'reactflow/dist/style.css' import { useDispatch, useSelector } from 'react-redux' import { useNavigate } from 'react-router-dom' import { usePrompt } from '../../utils/usePrompt' import { REMOVE_DIRTY, SET_DIRTY, SET_WORKFLOW, enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' // material-ui import { Toolbar, Box, AppBar, Button, Fab, CircularProgress } from '@mui/material' import { useTheme } from '@mui/material/styles' // project imports import CanvasNode from './CanvasNode' import ButtonEdge from './ButtonEdge' import CanvasHeader from './CanvasHeader' import AddNodes from './AddNodes' import EditNodes from './EditNodes' import ConfirmDialog from 'ui-component/dialog/ConfirmDialog' import TestWorkflowDialog from 'ui-component/dialog/TestWorkflowDialog' // API import nodesApi from 'api/nodes' import workflowsApi from 'api/workflows' import webhooksApi from 'api/webhooks' // Hooks import useApi from 'hooks/useApi' import useConfirm from 'hooks/useConfirm' // icons import { IconX, IconBolt } from '@tabler/icons' // third party import socketIOClient from 'socket.io-client' // utils import { generateWebhookEndpoint, getUniqueNodeId, checkIfNodeLabelUnique, addAnchors, getEdgeLabelName, checkMultipleTriggers } from 'utils/genericHelper' import useNotifier from 'utils/useNotifier' // const import { baseURL } from 'store/constant' const nodeTypes = { customNode: CanvasNode } const edgeTypes = { buttonedge: ButtonEdge } // ==============================|| CANVAS ||============================== // const Canvas = () => { const theme = useTheme() const navigate = useNavigate() const URLpath = document.location.pathname.toString().split('/') const workflowShortId = URLpath[URLpath.length - 1] && URLpath[URLpath.length - 1].startsWith('W') ? URLpath[URLpath.length - 1] : '' const { confirm } = useConfirm() const dispatch = useDispatch() const canvas = useSelector((state) => state.canvas) const [canvasDataStore, setCanvasDataStore] = useState(canvas) const [workflow, setWorkflow] = useState(null) const [isTestWorkflowDialogOpen, setTestWorkflowDialogOpen] = useState(false) const [testWorkflowDialogProps, setTestWorkflowDialogProps] = useState({}) const [isTestingWorkflow, setIsTestingWorkflow] = useState(false) // ==============================|| Snackbar ||============================== // useNotifier() const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) // ==============================|| ReactFlow ||============================== // const [nodes, setNodes, onNodesChange] = useNodesState() const [edges, setEdges, onEdgesChange] = useEdgesState() const [rfInstance, setRfInstance] = useState(null) const [selectedNode, setSelectedNode] = useState(null) const reactFlowWrapper = useRef(null) // ==============================|| Workflow API ||============================== // const getNodesApi = useApi(nodesApi.getAllNodes) const removeTestTriggersApi = useApi(nodesApi.removeTestTriggers) const deleteAllTestWebhooksApi = useApi(webhooksApi.deleteAllTestWebhooks) const createNewWorkflowApi = useApi(workflowsApi.createNewWorkflow) const testWorkflowApi = useApi(workflowsApi.testWorkflow) const updateWorkflowApi = useApi(workflowsApi.updateWorkflow) const getSpecificWorkflowApi = useApi(workflowsApi.getSpecificWorkflow) // ==============================|| Events & Actions ||============================== // const onConnect = (params) => { const newEdge = { ...params, type: 'buttonedge', id: `${params.source}-${params.sourceHandle}-${params.target}-${params.targetHandle}`, data: { label: getEdgeLabelName(params.sourceHandle) } } setEdges((eds) => addEdge(newEdge, eds)) setDirty() } const handleTestWorkflow = () => { try { if (workflow.deployed) { alert('Testing workflow requires stopping deployed workflow. Please stop deployed workflow first') return } const rfInstanceObject = rfInstance.toObject() const nodes = rfInstanceObject.nodes || [] setTestWorkflowDialogOpen(true) setTestWorkflowDialogProps({ title: 'Test Workflow', nodes: nodes.filter((nd) => !nd.id.includes('ifElse')) }) } catch (e) { console.error(e) } } const onStartingPointClick = (startingNodeId) => { try { const socket = socketIOClient(baseURL) const rfInstanceObject = rfInstance.toObject() const nodes = rfInstanceObject.nodes || [] const edges = rfInstanceObject.edges || [] setTestWorkflowDialogOpen(false) socket.on('connect', () => { const clientId = socket.id const node = nodes.find((nd) => nd.id === startingNodeId) const nodeData = node.data const body = { nodes, edges, clientId, nodeData } testWorkflowApi.request(startingNodeId, body) setNodes((nds) => nds.map((node) => { node.data = { ...node.data, outputResponses: { ...node.data.outputResponses, submit: null, needRetest: null }, selected: false } return node }) ) setIsTestingWorkflow(true) }) socket.on('testWorkflowNodeResponse', (value) => { const { nodeId, data, status } = value const node = nodes.find((nd) => nd.id === nodeId) if (node) { const outputValues = { submit: status === 'FINISHED' ? true : null, needRetest: status === 'FINISHED' ? null : true, output: data } const nodeData = node.data nodeData['outputResponses'] = outputValues setNodes((nds) => nds.map((node) => { if (node.id === nodeId) { node.data = { ...nodeData, selected: false } } return node }) ) } }) socket.on('testWorkflowNodeFinish', () => { setIsTestingWorkflow(false) socket.disconnect() }) } catch (e) { console.error(e) } } const handleLoadWorkflow = (file) => { try { const flowData = JSON.parse(file) const nodes = flowData.nodes || [] for (let i = 0; i < nodes.length; i += 1) { const nodeData = nodes[i].data if (nodeData.type === 'webhook') nodeData.webhookEndpoint = generateWebhookEndpoint() } setNodes(nodes) setEdges(flowData.edges || []) setDirty() } catch (e) { console.error(e) } } const handleDeployWorkflow = async () => { if (rfInstance) { const rfInstanceObject = rfInstance.toObject() const flowData = JSON.stringify(rfInstanceObject) try { // Always save workflow first let savedWorkflowResponse if (!workflow.shortId) { const newWorkflowBody = { name: workflow.name, deployed: false, flowData } const response = await workflowsApi.createNewWorkflow(newWorkflowBody) savedWorkflowResponse = response.data } else { const updateBody = { flowData } const response = await workflowsApi.updateWorkflow(workflow.shortId, updateBody) savedWorkflowResponse = response.data } dispatch({ type: REMOVE_DIRTY }) // Then deploy const response = await workflowsApi.deployWorkflow(savedWorkflowResponse.shortId) const deployedWorkflowResponse = response.data dispatch({ type: SET_WORKFLOW, workflow: deployedWorkflowResponse }) enqueueSnackbar({ message: 'Workflow deployed!', options: { key: new Date().getTime() + Math.random(), variant: 'success', action: (key) => ( ) } }) } catch (error) { const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` enqueueSnackbar({ message: errorData, options: { key: new Date().getTime() + Math.random(), variant: 'error', persist: true, action: (key) => ( ) } }) } } } const handleStopWorkflow = async () => { try { const response = await workflowsApi.deployWorkflow(workflow.shortId, { halt: true }) const stoppedWorkflowResponse = response.data dispatch({ type: SET_WORKFLOW, workflow: stoppedWorkflowResponse }) enqueueSnackbar({ message: 'Workflow stopped', options: { key: new Date().getTime() + Math.random(), variant: 'success', action: (key) => ( ) } }) } catch (error) { const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` enqueueSnackbar({ message: errorData, options: { key: new Date().getTime() + Math.random(), variant: 'error', persist: true, action: (key) => ( ) } }) } } const handleDeleteWorkflow = async () => { const confirmPayload = { title: `Delete`, description: `Delete workflow ${workflow.name}?`, confirmButtonName: 'Delete', cancelButtonName: 'Cancel' } const isConfirmed = await confirm(confirmPayload) if (isConfirmed) { try { await workflowsApi.deleteWorkflow(workflow.shortId) navigate(-1) } catch (error) { const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` enqueueSnackbar({ message: errorData, options: { key: new Date().getTime() + Math.random(), variant: 'error', persist: true, action: (key) => ( ) } }) } } } const handleSaveFlow = (workflowName) => { if (rfInstance) { setNodes((nds) => nds.map((node) => { node.data = { ...node.data, selected: false } return node }) ) const rfInstanceObject = rfInstance.toObject() const flowData = JSON.stringify(rfInstanceObject) if (!workflow.shortId) { const newWorkflowBody = { name: workflowName, deployed: false, flowData } createNewWorkflowApi.request(newWorkflowBody) } else { const updateBody = { name: workflowName, flowData } updateWorkflowApi.request(workflow.shortId, updateBody) } } } // eslint-disable-next-line const onNodeDoubleClick = useCallback((event, clickedNode) => { setSelectedNode(clickedNode) setNodes((nds) => nds.map((node) => { if (node.id === clickedNode.id) { node.data = { ...node.data, selected: true } } else { node.data = { ...node.data, selected: false } } return node }) ) }) const onNodeContextMenu = (event, clickedNode) => { event.preventDefault() setSelectedNode(clickedNode) setNodes((nds) => nds.map((node) => { if (node.id === clickedNode.id) { node.data = { ...node.data, selected: true } } else { node.data = { ...node.data, selected: false } } return node }) ) } // eslint-disable-next-line const onNodeLabelUpdate = useCallback((nodeLabel) => { setNodes((nds) => nds.map((node) => { if (node.id === selectedNode.id) { if (!checkIfNodeLabelUnique(nodeLabel, rfInstance.getNodes())) { enqueueSnackbar({ message: 'Duplicated node label', options: { key: new Date().getTime() + Math.random(), variant: 'error', persist: true, action: (key) => ( ) } }) } else { if (node.data.label !== nodeLabel) { setTimeout(() => setDirty(), 0) } node.data = { ...node.data, label: nodeLabel } } } return node }) ) }) // eslint-disable-next-line const onNodeValuesUpdate = useCallback((nodeFlowData) => { setNodes((nds) => nds.map((node) => { if (node.id === selectedNode.id) { setTimeout(() => setDirty(), 0) node.data = { ...node.data, ...nodeFlowData, selected: true } } return node }) ) }) const onDragOver = useCallback((event) => { event.preventDefault() event.dataTransfer.dropEffect = 'move' }, []) const onDrop = useCallback( (event) => { event.preventDefault() const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect() let nodeData = event.dataTransfer.getData('application/reactflow') // check if the dropped element is valid if (typeof nodeData === 'undefined' || !nodeData) { return } nodeData = JSON.parse(nodeData) // check if workflow contains multiple triggers/webhooks if ((nodeData.type === 'webhook' || nodeData.type === 'trigger') && checkMultipleTriggers(rfInstance.getNodes())) { enqueueSnackbar({ message: 'Workflow can only contains 1 trigger or webhook node', options: { key: new Date().getTime() + Math.random(), variant: 'error', persist: true, action: (key) => ( ) } }) return } if (nodeData.type === 'webhook') nodeData.webhookEndpoint = generateWebhookEndpoint() const position = rfInstance.project({ x: event.clientX - reactFlowBounds.left - 100, y: event.clientY - reactFlowBounds.top - 50 }) const newNodeId = getUniqueNodeId(nodeData, rfInstance.getNodes()) const newNode = { id: newNodeId, position, type: 'customNode', data: addAnchors(nodeData, rfInstance.getNodes(), newNodeId) } setSelectedNode(newNode) setNodes((nds) => nds.concat(newNode).map((node) => { if (node.id === newNode.id) { node.data = { ...node.data, selected: true } } else { node.data = { ...node.data, selected: false } } return node }) ) setTimeout(() => setDirty(), 0) }, // eslint-disable-next-line [rfInstance] ) const saveWorkflowSuccess = () => { dispatch({ type: REMOVE_DIRTY }) enqueueSnackbar({ message: 'Workflow saved', options: { key: new Date().getTime() + Math.random(), variant: 'success', action: (key) => ( ) } }) } const errorFailed = (message) => { enqueueSnackbar({ message, options: { key: new Date().getTime() + Math.random(), variant: 'error', persist: true, action: (key) => ( ) } }) } const setDirty = () => { dispatch({ type: SET_DIRTY }) } // ==============================|| useEffect ||============================== // // Get specific workflow successful useEffect(() => { if (getSpecificWorkflowApi.data) { const workflow = getSpecificWorkflowApi.data const initialFlow = workflow.flowData ? JSON.parse(workflow.flowData) : [] setNodes(initialFlow.nodes || []) setEdges(initialFlow.edges || []) dispatch({ type: SET_WORKFLOW, workflow }) } else if (getSpecificWorkflowApi.error) { const error = getSpecificWorkflowApi.error const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` errorFailed(`Failed to retrieve workflow: ${errorData}`) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [getSpecificWorkflowApi.data, getSpecificWorkflowApi.error]) // Create new workflow successful useEffect(() => { if (createNewWorkflowApi.data) { const workflow = createNewWorkflowApi.data dispatch({ type: SET_WORKFLOW, workflow }) saveWorkflowSuccess() window.history.replaceState(null, null, `/canvas/${workflow.shortId}`) } else if (createNewWorkflowApi.error) { const error = createNewWorkflowApi.error const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` errorFailed(`Failed to save workflow: ${errorData}`) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [createNewWorkflowApi.data, createNewWorkflowApi.error]) // Update workflow successful useEffect(() => { if (updateWorkflowApi.data) { dispatch({ type: SET_WORKFLOW, workflow: updateWorkflowApi.data }) saveWorkflowSuccess() } else if (updateWorkflowApi.error) { const error = updateWorkflowApi.error const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` errorFailed(`Failed to save workflow: ${errorData}`) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [updateWorkflowApi.data, updateWorkflowApi.error]) // Test workflow failed useEffect(() => { if (testWorkflowApi.error) { enqueueSnackbar({ message: 'Test workflow failed', options: { key: new Date().getTime() + Math.random(), variant: 'error', persist: true, action: (key) => ( ) } }) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [testWorkflowApi.error]) // Listen to edge button click remove redux event useEffect(() => { if (rfInstance) { const edges = rfInstance.getEdges() const toRemoveEdgeId = canvasDataStore.removeEdgeId.split(':')[0] setEdges(edges.filter((edge) => edge.id !== toRemoveEdgeId)) setDirty() } // eslint-disable-next-line react-hooks/exhaustive-deps }, [canvasDataStore.removeEdgeId]) useEffect(() => setWorkflow(canvasDataStore.workflow), [canvasDataStore.workflow]) // Initialization useEffect(() => { removeTestTriggersApi.request() deleteAllTestWebhooksApi.request() if (workflowShortId) { getSpecificWorkflowApi.request(workflowShortId) } else { setNodes([]) setEdges([]) dispatch({ type: SET_WORKFLOW, workflow: { name: 'Untitled workflow' } }) } getNodesApi.request() // Clear dirty state before leaving and remove any ongoing test triggers and webhooks return () => { removeTestTriggersApi.request() deleteAllTestWebhooksApi.request() setTimeout(() => dispatch({ type: REMOVE_DIRTY }), 0) } // eslint-disable-next-line react-hooks/exhaustive-deps }, []) useEffect(() => { setCanvasDataStore(canvas) }, [canvas]) useEffect(() => { function handlePaste(e) { const pasteData = e.clipboardData.getData('text') //TODO: prevent paste event when input focused, temporary fix: catch workflow syntax if (pasteData.includes('{"nodes":[') && pasteData.includes('],"edges":[')) { handleLoadWorkflow(pasteData) } } window.addEventListener('paste', handlePaste) return () => { window.removeEventListener('paste', handlePaste) } // eslint-disable-next-line react-hooks/exhaustive-deps }, []) usePrompt('You have unsaved changes! Do you want to navigate away?', canvasDataStore.isDirty) return ( <>
    theme.palette.primary.main} nodeColor={() => theme.palette.primary.main} nodeBorderRadius={2} /> {} {isTestingWorkflow && ( )}
    setTestWorkflowDialogOpen(false)} onItemClick={onStartingPointClick} />
    ) } export default Canvas ================================================ FILE: packages/ui/src/views/contracts/ContractDialog.js ================================================ import { createPortal } from 'react-dom' import PropTypes from 'prop-types' import { useState, useEffect } from 'react' import { useDispatch } from 'react-redux' import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' import { Avatar, Accordion, AccordionSummary, AccordionDetails, Box, Divider, Chip, Typography, Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, IconButton } from '@mui/material' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import { useTheme } from '@mui/material/styles' // third-party import * as Yup from 'yup' import lodash from 'lodash' import { ethers } from 'ethers' // project imports import InputParameters from 'views/inputs/InputParameters' import CredentialInput from 'views/inputs/CredentialInput' import EditVariableDialog from 'ui-component/dialog/EditVariableDialog' import { StyledButton } from 'ui-component/StyledButton' // Icons import { IconExclamationMark, IconCheck, IconX, IconArrowUpRightCircle, IconCopy } from '@tabler/icons' // API import contractsApi from 'api/contracts' // Hooks import useApi from 'hooks/useApi' // Const import { contract_details, networks, networkExplorers } from 'store/constant' // utils import { handleCredentialParams, initializeNodeData } from 'utils/genericHelper' import useNotifier from 'utils/useNotifier' const ContractDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const portalElement = document.getElementById('portal') const theme = useTheme() const dispatch = useDispatch() // ==============================|| Snackbar ||============================== // useNotifier() const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) const [contractDetails, setContractDetails] = useState(contract_details) const [contractData, setContractData] = useState({}) const [contractParams, setContractParams] = useState([]) const [contractValues, setContractValues] = useState({}) const [contractValidation, setContractValidation] = useState({}) const [expanded, setExpanded] = useState(false) const [invalidAddress, setInvalidAddress] = useState(false) const [invalidABI, setInvalidABI] = useState('') const [isReadyToAdd, setIsReadyToAdd] = useState(false) const [isEditVariableDialogOpen, setEditVariableDialog] = useState(false) const [editVariableDialogProps, setEditVariableDialogProps] = useState({}) const contractParamsType = ['networks', 'credentials', 'contractInfo'] const getSpecificContractApi = useApi(contractsApi.getSpecificContract) const handleAccordionChange = (expanded) => (event, isExpanded) => { setExpanded(isExpanded ? expanded : false) } const reset = () => { setContractData({}) setContractParams([]) setContractValues({}) setContractValidation({}) setInvalidAddress(false) setInvalidABI('') setIsReadyToAdd(false) setExpanded(false) } const checkIsReadyToAdd = () => { for (let i = 0; i < contractParamsType.length; i += 1) { const paramType = contractParamsType[i] if (!contractData[paramType] || !contractData[paramType].submit) { setIsReadyToAdd(false) return } } setIsReadyToAdd(true) } const onEditVariableDialogOpen = (input, values, arrayItemBody) => { const dialogProps = { input, values, arrayItemBody, cancelButtonName: 'Cancel', confirmButtonName: 'Save', hideVariables: true } setEditVariableDialogProps(dialogProps) setEditVariableDialog(true) } const addNewContract = async () => { const createNewContractBody = { network: contractData.networks.network, name: contractData.contractInfo.name, abi: contractData.contractInfo.abi, address: contractData.contractInfo.address, providerCredential: JSON.stringify(contractData.credentials) } try { const createResp = await contractsApi.createNewContract(createNewContractBody) if (createResp.data) { enqueueSnackbar({ message: 'New contract added', options: { key: new Date().getTime() + Math.random(), variant: 'success', action: (key) => ( ) } }) onConfirm() } } catch (error) { const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` enqueueSnackbar({ message: `Failed to add new contract: ${errorData}`, options: { key: new Date().getTime() + Math.random(), variant: 'error', persist: true, action: (key) => ( ) } }) onCancel() } } const saveContract = async () => { const saveContractBody = { network: contractData.networks.network, name: contractData.contractInfo.name, abi: contractData.contractInfo.abi, address: contractData.contractInfo.address, providerCredential: JSON.stringify(contractData.credentials) } try { const saveResp = await contractsApi.updateContract(dialogProps.id, saveContractBody) if (saveResp.data) { enqueueSnackbar({ message: 'Contract saved', options: { key: new Date().getTime() + Math.random(), variant: 'success', action: (key) => ( ) } }) onConfirm() } } catch (error) { const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` enqueueSnackbar({ message: `Failed to save contract: ${errorData}`, options: { key: new Date().getTime() + Math.random(), variant: 'error', persist: true, action: (key) => ( ) } }) onCancel() } } const deleteContract = async () => { try { const deleteResp = await contractsApi.deleteContract(dialogProps.id) if (deleteResp.data) { enqueueSnackbar({ message: 'Contract deleted', options: { key: new Date().getTime() + Math.random(), variant: 'success', action: (key) => ( ) } }) onConfirm() } } catch (error) { const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` enqueueSnackbar({ message: `Failed to delete contract: ${errorData}`, options: { key: new Date().getTime() + Math.random(), variant: 'error', persist: true, action: (key) => ( ) } }) onCancel() } } const fetchABI = async (formValues, paramsType) => { const selectedNetwork = networks.find((network) => network.name === contractData.networks.network) if (!selectedNetwork) return const body = { ...contractData, networks: { ...contractData.networks, uri: selectedNetwork.uri || '' } } const resp = await contractsApi.getContractABI(body) if (!resp.data) { const updateContractData = { ...contractData, [paramsType]: { ...formValues, submit: null } } setContractData(updateContractData) setInvalidABI('Unable to fetch ABI') return } else { const status = resp.data.status if (status === '0') { setInvalidABI('Unable to fetch ABI') return } const abi = resp.data.result setInvalidABI('') return abi === 'Invalid API Key' ? undefined : abi } } const valueChanged = (formValues, paramsType) => { const updateContractData = { ...contractData, [paramsType]: formValues } const index = contractParamsType.indexOf(paramsType) if (index >= 0 && index !== contractParamsType.length - 1) { for (let i = index + 1; i < contractParamsType.length; i += 1) { const paramType = contractParamsType[i] if (updateContractData[paramType]) updateContractData[paramType].submit = null } } setContractData(updateContractData) } const paramsChanged = (formParams, paramsType) => { // Because formParams options can be changed due to show hide options, // To avoid that, replace with original details options const credentialMethodParam = formParams.find((param) => param.name === 'credentialMethod') const credentialMethodParamIndex = formParams.findIndex((param) => param.name === 'credentialMethod') if (credentialMethodParam !== undefined) { const originalParam = contractDetails[paramsType].find((param) => param.name === 'credentialMethod') if (originalParam !== undefined) { formParams[credentialMethodParamIndex]['options'] = originalParam.options } } const updateContractDetails = { ...contractDetails, [paramsType]: formParams } setContractDetails(updateContractDetails) } const onSubmit = async (formValues, paramsType) => { if (formValues.address) { if (ethers.utils.isAddress(formValues.address)) { setInvalidAddress(false) const abi = await fetchABI(formValues, paramsType) if (abi) { const updateFormValues = { submit: true, ...formValues } updateFormValues.abi = abi const updateContractData = { ...contractData, [paramsType]: updateFormValues } setContractData(updateContractData) } else { const updateContractData = { ...contractData, [paramsType]: { ...formValues, submit: null } } setContractData(updateContractData) } } else { setInvalidAddress(true) const updateContractData = { ...contractData, [paramsType]: { ...formValues, submit: null } } setContractData(updateContractData) } } else { const updateContractData = { ...contractData, [paramsType]: formValues } setContractData(updateContractData) } const index = contractParamsType.indexOf(paramsType) if (index >= 0 && index !== contractParamsType.length - 1) { setExpanded(contractParamsType[index + 1]) } } const showHideOptions = (displayType, options) => { let returnOptions = options const toBeDeleteOptions = [] for (let i = 0; i < returnOptions.length; i += 1) { const option = returnOptions[i] const displayOptions = option[displayType] if (displayOptions) { Object.keys(displayOptions).forEach((path) => { const comparisonValue = displayOptions[path] const groundValue = lodash.get(contractData, path, '') if (Array.isArray(comparisonValue)) { if (displayType === 'show' && !comparisonValue.includes(groundValue)) { toBeDeleteOptions.push(option) } if (displayType === 'hide' && comparisonValue.includes(groundValue)) { toBeDeleteOptions.push(option) } } }) } } for (let i = 0; i < toBeDeleteOptions.length; i += 1) { returnOptions = returnOptions.filter((opt) => JSON.stringify(opt) !== JSON.stringify(toBeDeleteOptions[i])) } return returnOptions } const displayOptions = (params) => { let clonedParams = params for (let i = 0; i < clonedParams.length; i += 1) { const input = clonedParams[i] if (input.type === 'options') { input.options = showHideOptions('show', input.options) input.options = showHideOptions('hide', input.options) } } return clonedParams } const setYupValidation = (params) => { const validationSchema = {} for (let i = 0; i < params.length; i += 1) { const input = params[i] if (input.type === 'string' && !input.optional) { validationSchema[input.name] = Yup.string().required(`${input.label} is required. Type: ${input.type}`) } else if (input.type === 'number' && !input.optional) { validationSchema[input.name] = Yup.number().required(`${input.label} is required. Type: ${input.type}`) } else if ((input.type === 'options' || input.type === 'asyncOptions') && !input.optional) { validationSchema[input.name] = Yup.string().required(`${input.label} is required. Type: ${input.type}`) } } return validationSchema } const initializeFormValuesAndParams = (paramsType) => { const initialValues = {} let contractParams = displayOptions(lodash.cloneDeep(contractDetails[paramsType] || [])) contractParams = handleCredentialParams(contractParams, paramsType, contractDetails[paramsType], contractData) for (let i = 0; i < contractParams.length; i += 1) { const input = contractParams[i] // Load from contractData values if (paramsType in contractData && input.name in contractData[paramsType]) { initialValues[input.name] = contractData[paramsType][input.name] // Check if option value is still available from the list of options if (input.type === 'options') { const optionVal = input.options.find((option) => option.name === initialValues[input.name]) if (!optionVal) delete initialValues[input.name] } } else { // Load from contractParams default values initialValues[input.name] = input.default || '' } } initialValues.submit = null setContractValues(initialValues) setContractValidation(setYupValidation(contractParams)) setContractParams(contractParams) } const transformContractResponse = (contractResponseData, contractDetails) => { const contractData = { networks: {}, credentials: {}, contractInfo: {} } if (contractResponseData) { contractData.networks = { network: contractResponseData.network, submit: true } contractData.contractInfo = { ...contractResponseData, submit: true } if (contractResponseData.providerCredential) { try { contractData.credentials = JSON.parse(contractResponseData.providerCredential) } catch (e) { console.error(e) } } } else { contractData.networks = initializeNodeData(contractDetails.networks) contractData.credentials = initializeNodeData(contractDetails.credentials) contractData.contractInfo = initializeNodeData(contractDetails.contractInfo) } return contractData } // Get Contract Details from API useEffect(() => { if (getSpecificContractApi.data) { const contractResponseData = getSpecificContractApi.data setContractData(transformContractResponse(contractResponseData)) setExpanded('networks') } }, [getSpecificContractApi.data]) // Initialization useEffect(() => { if (show && dialogProps.type === 'ADD') { reset() setContractData(transformContractResponse(null, contractDetails)) setExpanded('networks') } else if (show && dialogProps.type === 'EDIT' && dialogProps.id) { reset() getSpecificContractApi.request(dialogProps.id) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [show, dialogProps]) // Initialize Parameters Initial Values & Validation useEffect(() => { if (contractDetails && contractData && expanded) { initializeFormValuesAndParams(expanded) checkIsReadyToAdd() } // eslint-disable-next-line react-hooks/exhaustive-deps }, [contractDetails, contractData, expanded]) const component = show ? ( {dialogProps.title} {dialogProps.type === 'ADD' && ( } label='You can only add contract which has been publicly verified' color='warning' /> )} {contractData && contractData.contractInfo && contractData.contractInfo.address && dialogProps.type === 'EDIT' && ( ADDRESS {contractData.contractInfo.address} navigator.clipboard.writeText(contractData.contractInfo.address)} > window.open( `${networkExplorers[contractData.networks.network]}/address/${contractData.contractInfo.address}`, '_blank' ) } > )} {/* networks */} } aria-controls='networks-content' id='networks-header'> Networks {contractData && contractData.networks && contractData.networks.submit && ( )} null} onEditVariableDialogOpen={onEditVariableDialogOpen} /> {/* credentials */} } aria-controls='credentials-content' id='credentials-header'> Credentials {contractData && contractData.credentials && contractData.credentials.submit && ( )} {/* contractInfo */} } aria-controls='contractInfo-content' id='contractInfo-header'> Contract Details {contractData && contractData.contractInfo && contractData.contractInfo.submit && ( )} null} onEditVariableDialogOpen={onEditVariableDialogOpen} /> {invalidAddress && ( } label='Invalid Contract Address' color='error' /> )} {invalidABI && ( } label={invalidABI} color='error' /> )} setEditVariableDialog(false)} onConfirm={(updateValues) => { valueChanged(updateValues, expanded) setEditVariableDialog(false) }} /> {dialogProps.type === 'EDIT' && ( deleteContract()}> Delete )} (dialogProps.type === 'ADD' ? addNewContract() : saveContract())} > {dialogProps.confirmButtonName} ) : null return createPortal(component, portalElement) } ContractDialog.propTypes = { show: PropTypes.bool, dialogProps: PropTypes.object, onCancel: PropTypes.func, onConfirm: PropTypes.func } export default ContractDialog ================================================ FILE: packages/ui/src/views/contracts/index.js ================================================ import { useEffect, useState } from 'react' import { useSelector } from 'react-redux' // material-ui import { Grid, Box, Stack } from '@mui/material' import { useTheme } from '@mui/material/styles' // project imports import MainCard from 'ui-component/cards/MainCard' import ItemCard from 'ui-component/cards/ItemCard' import ContractDialog from './ContractDialog' import ContractEmptySVG from 'assets/images/contract_empty.svg' import { TooltipWithParser } from 'ui-component/TooltipWithParser' import { StyledButton } from 'ui-component/StyledButton' // const import { gridSpacing } from 'store/constant' // API import contractsApi from 'api/contracts' // Hooks import useApi from 'hooks/useApi' // ==============================|| CONTRACTS ||============================== // const Contracts = () => { const theme = useTheme() const customization = useSelector((state) => state.customization) const [isLoading, setLoading] = useState(true) const [showDialog, setShowDialog] = useState(false) const [dialogProps, setDialogProps] = useState({}) const getAllContractsApi = useApi(contractsApi.getAllContracts) const addNew = () => { const dialogProp = { title: 'Add New Contract', type: 'ADD', cancelButtonName: 'Cancel', confirmButtonName: 'Add' } setDialogProps(dialogProp) setShowDialog(true) } const edit = (id) => { const dialogProp = { title: 'Edit Contract', type: 'EDIT', cancelButtonName: 'Cancel', confirmButtonName: 'Save', id } setDialogProps(dialogProp) setShowDialog(true) } const onConfirm = () => { setShowDialog(false) getAllContractsApi.request() } useEffect(() => { getAllContractsApi.request() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) useEffect(() => { setLoading(getAllContractsApi.loading) }, [getAllContractsApi.loading]) return ( <>

    Contracts 

    Add New
    {!isLoading && getAllContractsApi.data && getAllContractsApi.data.map((data, index) => ( edit(data._id)} data={data} /> ))} {!isLoading && (!getAllContractsApi.data || getAllContractsApi.data.length === 0) && ( ContractEmptySVG
    No Contracts Yet
    )}
    setShowDialog(false)} onConfirm={onConfirm} > ) } export default Contracts ================================================ FILE: packages/ui/src/views/executions/index.js ================================================ import { useState, useRef, useEffect } from 'react' import PropTypes from 'prop-types' import { SET_WORKFLOW, enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' import { useDispatch, useSelector } from 'react-redux' // material-ui import { useTheme } from '@mui/material/styles' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import { Box, List, Accordion, AccordionSummary, AccordionDetails, Paper, Popper, Chip, Stack, Typography, Button, IconButton } from '@mui/material' // third-party import PerfectScrollbar from 'react-perfect-scrollbar' import moment from 'moment' import ReactJson from 'react-json-view' // project imports import MainCard from 'ui-component/cards/MainCard' import Transitions from 'ui-component/extended/Transitions' import AttachmentDialog from 'ui-component/dialog/AttachmentDialog' import HTMLDialog from 'ui-component/dialog/HTMLDialog' import ExpandDataDialog from 'ui-component/dialog/ExpandDataDialog' import { StyledButton } from 'ui-component/StyledButton' // hooks import useConfirm from 'hooks/useConfirm' import useNotifier from 'utils/useNotifier' // icon import { IconTrash, IconX, IconArrowsMaximize } from '@tabler/icons' // API import executionsApi from 'api/executions' import workflowsApi from 'api/workflows' // utils import { copyToClipboard } from 'utils/genericHelper' // ==============================|| EXECUTIONS ||============================== // const Executions = ({ workflowShortId, execution, executionCount, isExecutionOpen, anchorEl }) => { const theme = useTheme() const customization = useSelector((state) => state.customization) const [expanded, setExpanded] = useState(false) const [open, setOpen] = useState(false) const [showHTMLDialog, setShowHTMLDialog] = useState(false) const [HTMLDialogProps, setHTMLDialogProps] = useState({}) const [showAttachmentDialog, setShowAttachmentDialog] = useState(false) const [attachmentDialogProps, setAttachmentDialogProps] = useState({}) const [showExpandDialog, setShowExpandDialog] = useState(false) const [expandDialogProps, setExpandDialogProps] = useState({}) const dispatch = useDispatch() const varPrevOpen = useRef(open) const { confirm } = useConfirm() useNotifier() const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) const handleAccordionChange = (executionShortId) => (event, isExpanded) => { setExpanded(isExpanded ? executionShortId : false) } const setChipColor = (execState) => { if (execState === 'INPROGRESS') return theme.palette.warning.dark if (execState === 'FINISHED') return theme.palette.success.dark if (execState === 'ERROR') return theme.palette.error.dark if (execState === 'TERMINATED' || execState === 'TIMEOUT') return theme.palette.grey['700'] return theme.palette.primary.dark } const setChipBgColor = (execState) => { if (execState === 'INPROGRESS') return theme.palette.warning.light if (execState === 'FINISHED') return theme.palette.success.light if (execState === 'ERROR') return theme.palette.error.light if (execState === 'TERMINATED' || execState === 'TIMEOUT') return theme.palette.grey['300'] return theme.palette.primary.light } const openAttachmentDialog = (executionData) => { const dialogProp = { title: 'Attachments', executionData } setAttachmentDialogProps(dialogProp) setShowAttachmentDialog(true) } const openHTMLDialog = (executionData) => { const dialogProp = { title: 'HTML', executionData } setHTMLDialogProps(dialogProp) setShowHTMLDialog(true) } const onExpandDialogClicked = (executionData, nodeLabel) => { const dialogProp = { title: `Execution Data: ${nodeLabel}`, data: executionData } setExpandDialogProps(dialogProp) setShowExpandDialog(true) } const deleteExecution = async (e, executionShortId) => { e.stopPropagation() const confirmPayload = { title: `Delete`, description: `Delete execution ${executionShortId}?`, confirmButtonName: 'Delete', cancelButtonName: 'Cancel' } const isConfirmed = await confirm(confirmPayload) if (isConfirmed) { try { const executionResp = await executionsApi.deleteExecution(executionShortId) if (executionResp.data) { const workflowResponse = await workflowsApi.getSpecificWorkflow(workflowShortId) if (workflowResponse.data) dispatch({ type: SET_WORKFLOW, workflow: workflowResponse.data }) } enqueueSnackbar({ message: 'Execution deleted!', options: { key: new Date().getTime() + Math.random(), variant: 'success', action: (key) => ( ) } }) } catch (error) { const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` enqueueSnackbar({ message: errorData, options: { key: new Date().getTime() + Math.random(), variant: 'error', persist: true, action: (key) => ( ) } }) } } } // Handle Accordian useEffect(() => { varPrevOpen.current = open }, [open]) useEffect(() => { setOpen(isExecutionOpen) }, [isExecutionOpen]) return ( <> {({ TransitionProps }) => ( {executionCount} Executions {executionCount === 0 && execution.length === 0 && No executions yet} {executionCount > 0 && execution.length > 0 && ( {execution && execution.map((exec, index) => ( } aria-controls={`${exec.shortId}-content`} id={`${exec.shortId}-header`} > {exec.shortId} {exec.state && ( )} {moment(exec.createdDate).format( 'MMMM Do YYYY, h:mm:ss A z' )} deleteExecution(e, exec.shortId)} > {JSON.parse(exec.executionData).map((execData, execDataIndex) => ( {execData.nodeLabel} copyToClipboard(e)} /> onExpandDialogClicked(execData.data, execData.nodeLabel) } >
    {execData.data.map((execObj, execObjIndex) => (
    {execObj.html && ( HTML )} {execObj.html && (
    )} {execObj.html && ( openHTMLDialog(execData.data) } > View HTML )} {execObj.attachments && ( Attachments )} {execObj.attachments && execObj.attachments.map( (attachment, attchIndex) => (
    Item {execObjIndex} |{' '} {attachment.filename ? attachment.filename : `Attachment ${attchIndex}`} openAttachmentDialog( execData.data ) } > View Attachment
    ) )}
    ))}
    ))} ))} )} )} setShowAttachmentDialog(false)} > setShowHTMLDialog(false)}> setShowExpandDialog(false)} > ) } Executions.propTypes = { workflowShortId: PropTypes.string, execution: PropTypes.array, executionCount: PropTypes.number, isExecutionOpen: PropTypes.bool, anchorEl: PropTypes.any } export default Executions ================================================ FILE: packages/ui/src/views/inputs/ArrayInputParameters.js ================================================ import { useSelector } from 'react-redux' import PropTypes from 'prop-types' import { forwardRef } from 'react' // material-ui import { Box, Switch, FormControl, OutlinedInput, Popper, TextField, Typography, Stack, Button } from '@mui/material' import Autocomplete, { autocompleteClasses } from '@mui/material/Autocomplete' import { useTheme, styled } from '@mui/material/styles' import { TooltipWithParser } from '../../ui-component/TooltipWithParser' import { DarkCodeEditor } from 'ui-component/editor/DarkCodeEditor' import { LightCodeEditor } from 'ui-component/editor/LightCodeEditor' // icons import { IconX, IconUpload } from '@tabler/icons' // third party import lodash from 'lodash' import PerfectScrollbar from 'react-perfect-scrollbar' import DatePicker from 'react-datepicker' import 'react-datepicker/dist/react-datepicker.css' // utils import { convertDateStringToDateObject, getFileName, getFolderName } from 'utils/genericHelper' //css import './InputParameters.css' import { StyledFab } from 'ui-component/StyledFab' const StyledPopper = styled(Popper)({ boxShadow: '0px 8px 10px -5px rgb(0 0 0 / 20%), 0px 16px 24px 2px rgb(0 0 0 / 14%), 0px 6px 30px 5px rgb(0 0 0 / 12%)', borderRadius: '10px', [`& .${autocompleteClasses.listbox}`]: { boxSizing: 'border-box', '& ul': { padding: 10, margin: 10 } } }) const DateCustomInput = forwardRef(function DateCustomInput({ value, onClick }, ref) { return ( ) }) DateCustomInput.propTypes = { value: PropTypes.string, onClick: PropTypes.func } // ==============================|| ARRAY INPUT PARAMETERS ||============================== // const ArrayInputParameters = ({ initialValues, arrayParams, paramsType, arrayGroupName, errors, onArrayInputChange, onArrayInputBlur, onArrayItemRemove, onArrayItemMouseUp, onEditVariableDialogOpen }) => { const theme = useTheme() const customization = useSelector((state) => state.customization) const processUpdateValues = (inputValue, inputName, values, index) => { const updateArrayValues = { ...values, [inputName]: inputValue } const updateInitialValues = initialValues updateInitialValues[index] = updateArrayValues return updateInitialValues } const onInputChange = (inputValue, inputName, values, index) => { const updateInitialValues = processUpdateValues(inputValue, inputName, values, index) onArrayInputChange(updateInitialValues) } const onInputBlur = (inputValue, inputName, values, index) => { const updateInitialValues = processUpdateValues(inputValue, inputName, values, index) onArrayInputBlur(updateInitialValues) } const onRemoveClick = (index) => { const updateInitialValues = initialValues updateInitialValues.splice(index, 1) onArrayItemRemove(updateInitialValues) onArrayItemMouseUp(false) } const onMouseUp = (e, inputName, valueIndex) => { const cursorPosition = e.target.selectionEnd const textBeforeCursorPosition = e.target.value.substring(0, cursorPosition) const textAfterCursorPosition = e.target.value.substring(cursorPosition, e.target.value.length) const path = `${paramsType}.${arrayGroupName}[${valueIndex}].${inputName}` const body = { textBeforeCursorPosition, textAfterCursorPosition, path, paramsType } onArrayItemMouseUp(true, body) } const handleFolderUpload = (e, values, inputName, index) => { if (!e.target.files) return const files = e.target.files const reader = new FileReader() function readFile(fileIndex, base64Array) { if (fileIndex >= files.length) { onInputChange(JSON.stringify(base64Array), inputName, values, index) return } const file = files[fileIndex] reader.onload = (evt) => { if (!evt?.target?.result) { return } const { result } = evt.target const value = result + `,filepath:${file.webkitRelativePath}` base64Array.push(value) readFile(fileIndex + 1, lodash.cloneDeep(base64Array)) } reader.readAsDataURL(file) } readFile(0, []) } const handleFileUpload = (e, onInputChange, values, inputName, index) => { if (!e.target.files) { return } const file = e.target.files[0] const { name } = file const reader = new FileReader() reader.onload = (evt) => { if (!evt?.target?.result) { return } const { result } = evt.target const value = result + `,filename:${name}` onInputChange(value, inputName, values, index) } reader.readAsDataURL(file) } const findMatchingOptions = (options, value) => options.find((option) => option.name === value) const getDefaultOptionValue = () => '' return ( <> {arrayParams.map((_, index) => { const params = arrayParams[index] || [] const values = initialValues[index] || {} return ( {arrayParams.length > 1 && ( onRemoveClick(index)} > )} {params.map((input, paramIndex) => { if (input.type === 'file' || input.type === 'folder') { const inputName = input.name return ( 0 && errors[index] ? Boolean(errors[index][inputName]) : false} > {input.label} {input.description && } {input.type === 'file' && ( {values[inputName] ? getFileName(values[inputName]) : 'Choose a file to upload'} )} {input.type === 'folder' && ( {values[inputName] ? getFolderName(values[inputName]) : 'Choose a folder to upload'} )} ) } if (input.type === 'json' || input.type === 'code') { const inputName = input.name return ( 0 && errors[index] ? Boolean(errors[index][inputName]) : false} > {input.label} {input.description && } e.stopPropagation()} > {customization.isDarkMode ? ( onInputChange(code, inputName, values, index)} placeholder={input.placeholder} type={input.type} onMouseUp={(e) => onMouseUp(e, inputName, index)} onBlur={(e) => { onInputBlur(e.target.value, inputName, values, index) onMouseUp(e, inputName, index) }} style={{ fontSize: '0.875rem', minHeight: '200px', width: '100%' }} /> ) : ( onInputChange(code, inputName, values, index)} placeholder={input.placeholder} type={input.type} onMouseUp={(e) => onMouseUp(e, inputName, index)} onBlur={(e) => { onInputBlur(e.target.value, inputName, values, index) onMouseUp(e, inputName, index) }} style={{ fontSize: '0.875rem', minHeight: '200px', width: '100%' }} /> )} ) } if (input.type === 'date') { const inputName = input.name return ( 0 && errors[index] ? Boolean(errors[index][inputName]) : false} > {input.label} {input.description && } } selected={convertDateStringToDateObject(values[inputName]) || null} showTimeInput isClearable timeInputLabel='Time:' dateFormat='MM/dd/yyyy h:mm aa' onChange={(date) => { const inputValue = date ? date.toISOString() : null onInputChange(inputValue, inputName, values, index) onArrayItemMouseUp(false) }} /> ) } if (input.type === 'string' || input.type === 'password' || input.type === 'number') { const inputName = input.name return ( 0 && errors[index] ? Boolean(errors[index][inputName]) : false} > {input.label} {input.description && } {(input.type === 'string' || input.type === 'number') && ( )} { const inputValue = e.target.value onInputBlur(inputValue, inputName, values, index) onMouseUp(e, inputName, index) }} onChange={(e) => { const inputValue = e.target.value onInputChange(inputValue, inputName, values, index) }} onMouseUp={(e) => onMouseUp(e, inputName, index)} /> ) } if (input.type === 'boolean') { const inputName = input.name return ( 0 && errors[index] ? Boolean(errors[index][inputName]) : false} > {input.label} {input.description && } { onInputChange(event.target.checked, inputName, values, index) }} inputProps={{ 'aria-label': 'controlled' }} /> ) } if (input.type === 'options') { const inputName = input.name const availableOptions = input.options || [] return ( {input.label} {input.description && } onArrayItemMouseUp(false)} options={availableOptions} value={findMatchingOptions(availableOptions, values[inputName]) || getDefaultOptionValue()} onChange={(e, selection) => { const value = selection ? selection.name : '' onInputBlur(value, inputName, values, index) }} PopperComponent={StyledPopper} renderInput={(params) => ( 0 && errors[index] ? Boolean(errors[index][inputName]) : false } /> )} renderOption={(props, option) => (
    {option.label} {option.description && {option.description}}
    )} />
    ) } return null })}
    ) })} ) } ArrayInputParameters.propTypes = { initialValues: PropTypes.array, arrayParams: PropTypes.array, paramsType: PropTypes.string, arrayGroupName: PropTypes.string, errors: PropTypes.array, onArrayInputChange: PropTypes.func, onArrayInputBlur: PropTypes.func, onArrayItemRemove: PropTypes.func, onArrayItemMouseUp: PropTypes.func, onEditVariableDialogOpen: PropTypes.func } export default ArrayInputParameters ================================================ FILE: packages/ui/src/views/inputs/AsyncSelectWrapper.js ================================================ import { useState, useEffect } from 'react' import { useSelector } from 'react-redux' import PropTypes from 'prop-types' // material-ui import { Typography, Stack } from '@mui/material' import { useTheme } from '@mui/material/styles' // project imports import OptionParamsResponse from './OptionParamsResponse' import { TooltipWithParser } from '../../ui-component/TooltipWithParser' // third party import lodash from 'lodash' import AsyncSelect from 'react-select/async' import axios from 'axios' // icons import { IconX } from '@tabler/icons' // Constant import { baseURL } from 'store/constant' // ==============================|| ASYNC SELECT WRAPPER ||============================== // const AsyncSelectWrapper = ({ title, description, value, loadMethod, loadFromDbCollections, nodeFlowData, error, onChange, onMenuOpen, onSetError }) => { const theme = useTheme() const customization = useSelector((state) => state.customization) const customStyles = { option: (provided, state) => ({ ...provided, paddingTop: 15, paddingBottom: 15, paddingLeft: 20, paddingRight: 20, cursor: 'pointer', fontWeight: '500', backgroundColor: customization.isDarkMode ? state.isSelected ? '#233345' : theme.palette.primary.light : state.isSelected ? theme.palette.primary.light : '', color: customization.isDarkMode ? 'white' : 'black', '&:hover': { backgroundColor: customization.isDarkMode ? '#233345' : theme.palette.grey['200'] } }), control: (provided) => ({ ...provided, cursor: 'text', backgroundColor: theme.palette.asyncSelect.main, paddingTop: 8, paddingBottom: 8, paddingRight: 6, paddingLeft: 6, borderRadius: 12, border: customization.isDarkMode ? 'none' : `solid 1px ${theme.palette.grey['400']}`, '&:hover': { borderColor: theme.palette.grey['700'] } }), singleValue: (provided) => ({ ...provided, color: customization.isDarkMode ? 'white' : 'black', fontWeight: '600' }), menuList: (provided) => ({ ...provided, backgroundColor: customization.isDarkMode ? theme.palette.primary.light : '', boxShadow: '0px 8px 10px -5px rgb(0 0 0 / 20%), 0px 16px 24px 2px rgb(0 0 0 / 14%), 0px 6px 30px 5px rgb(0 0 0 / 12%)' }) } const [asyncOptions, setAsyncOptions] = useState([]) const getSelectedValue = (value) => asyncOptions.find((option) => option.name === value) const getDefaultOptionValue = () => '' const formatErrorMessage = (error) => { if (error) return `*${error.replace(/["]/g, '')}` return '' } const showHideOptions = (options) => { let returnOptions = options const toBeDeleteOptions = [] const displayTypes = ['show', 'hide'] for (let x = 0; x < displayTypes.length; x += 1) { const displayType = displayTypes[x] for (let i = 0; i < returnOptions.length; i += 1) { const option = returnOptions[i] const displayOptions = option[displayType] if (displayOptions) { Object.keys(displayOptions).forEach((path) => { const comparisonValue = displayOptions[path] const groundValue = lodash.get(nodeFlowData, path, '') if (Array.isArray(comparisonValue)) { if (displayType === 'show' && !comparisonValue.includes(groundValue)) { toBeDeleteOptions.push(option) } if (displayType === 'hide' && comparisonValue.includes(groundValue)) { toBeDeleteOptions.push(option) } } else if (typeof comparisonValue === 'string') { if ( displayType === 'show' && !(comparisonValue === groundValue || new RegExp(comparisonValue).test(groundValue)) ) { toBeDeleteOptions.push(option) } if ( displayType === 'hide' && (comparisonValue === groundValue || new RegExp(comparisonValue).test(groundValue)) ) { toBeDeleteOptions.push(option) } } }) } } } for (let i = 0; i < toBeDeleteOptions.length; i += 1) { returnOptions = returnOptions.filter((opt) => JSON.stringify(opt) !== JSON.stringify(toBeDeleteOptions[i])) } return returnOptions } const loadOptions = (inputValue, callback) => { axios .post(`${baseURL}/api/v1/node-load-method/${nodeFlowData.name}`, { ...nodeFlowData, loadMethod, loadFromDbCollections }) .then((response) => { const data = response.data const filteredOption = (data || []).filter((i) => i.label.toLowerCase().includes(inputValue.toLowerCase())) const options = showHideOptions(filteredOption) setAsyncOptions(options) callback(options) }) } const formatOptionLabel = ({ label, description }, { context }) => ( <> {context === 'menu' && (
    {label}
    {description && {description}}
    )} {context === 'value' && (
    {label}
    )} ) useEffect(() => () => setAsyncOptions([]), []) useEffect(() => { if (value !== undefined) { const selectedOption = asyncOptions.find((option) => option.name === value) if (!selectedOption) { onSetError() } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [asyncOptions]) return ( <> {title} {description && }
    option.label} getOptionValue={(option) => option.name} loadOptions={loadOptions} defaultOptions onChange={onChange} onMenuOpen={onMenuOpen} />
    {error && {formatErrorMessage(error)}} ) } AsyncSelectWrapper.propTypes = { title: PropTypes.string, description: PropTypes.string, value: PropTypes.string, loadMethod: PropTypes.string, loadFromDbCollections: PropTypes.array, nodeFlowData: PropTypes.object, error: PropTypes.string, onChange: PropTypes.func, onMenuOpen: PropTypes.func, onSetError: PropTypes.func } export default AsyncSelectWrapper ================================================ FILE: packages/ui/src/views/inputs/CredentialInput.js ================================================ import { useState, useEffect } from 'react' import { useSelector } from 'react-redux' import PropTypes from 'prop-types' // material-ui import { Box, Button, FormControl, Stack, OutlinedInput, Popper, TextField, Typography, IconButton, Switch } from '@mui/material' import Autocomplete, { autocompleteClasses } from '@mui/material/Autocomplete' import { useTheme, styled } from '@mui/material/styles' // third party import * as Yup from 'yup' import { Formik } from 'formik' import PerfectScrollbar from 'react-perfect-scrollbar' // project imports import AnimateButton from 'ui-component/extended/AnimateButton' import { StyledButton } from 'ui-component/StyledButton' import { DarkCodeEditor } from 'ui-component/editor/DarkCodeEditor' import { LightCodeEditor } from 'ui-component/editor/LightCodeEditor' // API import credentialApi from 'api/credential' import oauth2Api from 'api/oauth2' // Hooks import useApi from 'hooks/useApi' import useScriptRef from 'hooks/useScriptRef' // icons import { IconTrash, IconCopy } from '@tabler/icons' import gLoginLogo from 'assets/images/google-login-white.png' //css import './InputParameters.css' import { TooltipWithParser } from '../../ui-component/TooltipWithParser' const StyledPopper = styled(Popper)({ boxShadow: '0px 8px 10px -5px rgb(0 0 0 / 20%), 0px 16px 24px 2px rgb(0 0 0 / 14%), 0px 6px 30px 5px rgb(0 0 0 / 12%)', borderRadius: '10px', [`& .${autocompleteClasses.listbox}`]: { boxSizing: 'border-box', '& ul': { padding: 10, margin: 10 } } }) const ADD_NEW_CREDENTIAL = '+ Add New Credential' // ==============================|| CREDENTIAL INPUT ||============================== // const CredentialInput = ({ initialParams, paramsType, initialValues, initialValidation, valueChanged, paramsChanged, onSubmit, ...others }) => { const scriptedRef = useScriptRef() const theme = useTheme() const customization = useSelector((state) => state.customization) const [credentialValidation, setCredentialValidation] = useState({}) const [credentialValues, setCredentialValues] = useState({}) const [nodeCredentialName, setNodeCredentialName] = useState('') const [credentialParams, setCredentialParams] = useState([]) const [credentialOptions, setCredentialOptions] = useState([]) const [oAuth2RedirectURL, setOAuth2RedirectURL] = useState('') const getCredentialParamsApi = useApi(credentialApi.getCredentialParams) const getRegisteredCredentialsApi = useApi(credentialApi.getCredentials) const onChanged = (values) => { const updateValues = values updateValues.submit = null valueChanged(updateValues, paramsType) } const getCredentialRequestBody = (values) => { if (credentialParams.length === 0) throw new Error('Credential params empty') const credentialData = {} for (let i = 0; i < credentialParams.length; i += 1) { const credParamName = credentialParams[i].name if (credParamName in values) credentialData[credParamName] = values[credParamName] } delete credentialData.name const credBody = { name: values.name, nodeCredentialName: values.credentialMethod, credentialData } return credBody } const updateYupValidation = (inputName, validationKey) => { const updateValidation = { ...credentialValidation, [inputName]: Yup.object({ [validationKey]: Yup.string().required(`${inputName} is required`) }) } setCredentialValidation(updateValidation) } const clearCredentialParams = () => { const updateParams = initialParams.filter((item) => credentialParams.every((paramItem) => item.name !== paramItem.name)) setCredentialParams([]) setOAuth2RedirectURL('') paramsChanged(updateParams, paramsType) } const clearCredentialParamsValues = (value) => { let updateValues = JSON.parse(JSON.stringify(credentialValues)) for (let i = 0; i < credentialParams.length; i += 1) { const credParamName = credentialParams[i].name if (credParamName in updateValues) delete updateValues[credParamName] } updateValues = { ...updateValues, registeredCredential: value } valueChanged(updateValues, paramsType) } const onDeleteCredential = async (credentialId) => { const response = await credentialApi.deleteCredential(credentialId) if (response.data) { clearCredentialParams() clearCredentialParamsValues('') } } const openOAuth2PopUpWindow = (oAuth2PopupURL) => { const windowWidth = 500 const windowHeight = 400 const left = window.screenX + (window.outerWidth - windowWidth) / 2 const top = window.screenY + (window.outerHeight - windowHeight) / 2.5 const title = `Connect Credential` const url = oAuth2PopupURL const popup = window.open(url, title, `width=${windowWidth},height=${windowHeight},left=${left},top=${top}`) return popup } const findMatchingOptions = (options, value) => options.find((option) => option.name === value) const getDefaultOptionValue = () => '' // getRegisteredCredentialsApi successful useEffect(() => { if (getRegisteredCredentialsApi.data) { const credentialOptions = [] if (getRegisteredCredentialsApi.data.length) { for (let i = 0; i < getRegisteredCredentialsApi.data.length; i += 1) { credentialOptions.push({ _id: getRegisteredCredentialsApi.data[i]._id, name: getRegisteredCredentialsApi.data[i].name }) } } credentialOptions.push({ name: ADD_NEW_CREDENTIAL }) setCredentialOptions(credentialOptions) if (initialParams.find((prm) => prm.name === 'registeredCredential')) { updateYupValidation('registeredCredential', 'name') } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [getRegisteredCredentialsApi.data]) // getCredentialParamsApi successful useEffect(() => { if (getCredentialParamsApi.data) { const newCredentialParams = getCredentialParamsApi.data.credentials const credentialNameParam = { label: 'Credential Name', name: 'name', type: 'string', default: '' } newCredentialParams.unshift(credentialNameParam) setCredentialParams(newCredentialParams) const updateParams = initialParams for (let i = 0; i < newCredentialParams.length; i += 1) { const credParamName = newCredentialParams[i].name if (initialParams.find((prm) => prm.name === credParamName) === undefined) { updateParams.push(newCredentialParams[i]) } } paramsChanged(updateParams, paramsType) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [getCredentialParamsApi.data]) // Initialize values useEffect(() => { setCredentialValues(initialValues) if (initialValues && initialValues.credentialMethod) { getRegisteredCredentialsApi.request(initialValues.credentialMethod) setNodeCredentialName(initialValues.credentialMethod) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [initialValues]) // Initialize validation useEffect(() => { setCredentialValidation(initialValidation) }, [initialValidation]) return ( <> { try { if (scriptedRef.current) { const isAddNewCredential = values && values.registeredCredential && values.registeredCredential.name === ADD_NEW_CREDENTIAL if (!isAddNewCredential && (credentialParams.length === 0 || !values.credentialMethod)) { onSubmit(values.credentialMethod ? { ...values, submit: true } : { submit: true }, paramsType) setStatus({ success: true }) setSubmitting(false) } else { const body = getCredentialRequestBody(values) let response if (isAddNewCredential) { response = await credentialApi.createNewCredential(body) } else { response = await credentialApi.updateCredential(values.registeredCredential._id, body) } if (response.data) { // Open oAuth2 window if (values.credentialMethod.toLowerCase().includes('oauth2')) { const oAuth2PopupURL = await oauth2Api.geOAuth2PopupURL(response.data._id) const popUpWindow = openOAuth2PopUpWindow(oAuth2PopupURL.data) const oAuth2Completed = async (event) => { if (event.data === 'success') { window.removeEventListener('message', oAuth2Completed, false) const submitValues = { credentialMethod: values.credentialMethod, registeredCredential: { _id: response.data._id, name: response.data.name }, submit: true } clearCredentialParams() onSubmit(submitValues, paramsType) setStatus({ success: true }) setSubmitting(false) if (popUpWindow) { popUpWindow.close() } } } window.addEventListener('message', oAuth2Completed, false) return } const submitValues = { credentialMethod: values.credentialMethod, registeredCredential: { _id: response.data._id, name: response.data.name }, submit: true } clearCredentialParams() onSubmit(submitValues, paramsType) setStatus({ success: true }) setSubmitting(false) } else { throw new Error(response) } } } } catch (err) { console.error(err) if (scriptedRef.current) { setStatus({ success: false }) setErrors({ submit: err.message }) setSubmitting(false) } } }} > {({ errors, handleBlur, handleChange, handleSubmit, setFieldValue, isSubmitting, values }) => (
    {initialParams.map((input) => { if (input.type === 'options') { const inputName = input.name const availableOptions = input.options || [] return ( {input.label} {input.description && } { const value = selection ? selection.name : '' setFieldValue(inputName, value) const overwriteValues = { [inputName]: value } onChanged(overwriteValues) clearCredentialParams() if (selection) { getRegisteredCredentialsApi.request(value) setNodeCredentialName(value) } else { setCredentialOptions([]) } }} onBlur={handleBlur} PopperComponent={StyledPopper} renderInput={(params) => ( )} renderOption={(props, option) => (
    {option.label} {option.description && ( {option.description} )}
    )} /> {errors[inputName] && ( *{errors[inputName]} )}
    ) } return null })} {initialParams.find((prm) => prm.name === 'registeredCredential') && ( Registered Credential option.name || ' '} onChange={async (e, selectedCredential) => { setFieldValue( 'registeredCredential', selectedCredential !== null ? selectedCredential : initialValues.registeredCredential ) const overwriteValues = { ...values, registeredCredential: selectedCredential } onChanged(overwriteValues) if (selectedCredential) { if (selectedCredential.name !== ADD_NEW_CREDENTIAL) { const resp = await credentialApi.getSpecificCredential(selectedCredential._id) if (resp.data) { const updateValues = { ...overwriteValues, ...resp.data.credentialData, name: resp.data.name } valueChanged(updateValues, paramsType) } } else { clearCredentialParamsValues(selectedCredential) } getCredentialParamsApi.request(nodeCredentialName) if (values.credentialMethod.toLowerCase().includes('oauth2')) { const redirectURLResp = await oauth2Api.geOAuth2RedirectURL() if (redirectURLResp.data) setOAuth2RedirectURL(redirectURLResp.data) } } }} onInputChange={(e, value) => { if (!value) { clearCredentialParams() clearCredentialParamsValues('') } }} onBlur={handleBlur} PopperComponent={StyledPopper} renderInput={(params) => ( )} renderOption={(props, option) => (
    {option.name}
    )} /> {errors.registeredCredential && ( *Registered Credential is required )}
    )} {values && values.registeredCredential && values.registeredCredential._id && ( )} {oAuth2RedirectURL && (
    OAuth2 Redirect URL {oAuth2RedirectURL} navigator.clipboard.writeText(oAuth2RedirectURL)} >
    )} {values.credentialMethod && credentialParams.map((input) => { if (input.type === 'json') { const inputName = input.name return ( {input.label} {input.description && } e.stopPropagation()} > {customization.isDarkMode ? ( { setFieldValue(inputName, code) }} placeholder={input.placeholder} type={input.type} onBlur={(e) => { const overwriteValues = { ...values, [inputName]: e.target.value } onChanged(overwriteValues) }} style={{ fontSize: '0.875rem', minHeight: '200px', width: '100%' }} /> ) : ( { setFieldValue(inputName, code) }} placeholder={input.placeholder} type='json' onBlur={(e) => { const overwriteValues = { ...values, [inputName]: e.target.value } onChanged(overwriteValues) }} style={{ fontSize: '0.875rem', minHeight: '200px', width: '100%' }} /> )} {errors[inputName] && ( *{errors[inputName]} )} ) } if (input.type === 'string' || input.type === 'password' || input.type === 'number') { const inputName = input.name return ( {input.label} {input.description && } { handleBlur(e) onChanged(values) }} onChange={handleChange} /> {errors[inputName] && ( *{errors[inputName]} )} ) } if (input.type === 'boolean') { const inputName = input.name return ( {input.label} {input.description && } { setFieldValue(inputName, event.target.checked) const overwriteValues = { ...values, [inputName]: event.target.checked } onChanged(overwriteValues) }} inputProps={{ 'aria-label': 'controlled' }} /> ) } if (input.type === 'options') { const inputName = input.name const availableOptions = input.options || [] return ( {input.label} {input.description && } { const value = selection ? selection.name : '' setFieldValue(inputName, value) const overwriteValues = { ...values, [inputName]: value } onChanged(overwriteValues) }} onBlur={handleBlur} PopperComponent={StyledPopper} renderInput={(params) => ( )} renderOption={(props, option) => (
    {option.label} {option.description && ( {option.description} )}
    )} /> {errors[inputName] && ( *{errors[inputName]} )}
    ) } return null })} {!(values.credentialMethod || '').toLowerCase().includes('google') && ( 0} fullWidth size='large' type='submit' variant='contained' color='secondary' > {values && values.registeredCredential && (values.registeredCredential.name === ADD_NEW_CREDENTIAL || credentialParams.length) ? 'Save and Continue' : 'Continue'} )} {(values.credentialMethod || '').toLowerCase().includes('google') && ( 0} fullWidth size='large' type='submit' variant='contained' color='secondary' sx={{ p: 0, margin: 0 }} >
    Google Login
    )}
    )}
    ) } CredentialInput.propTypes = { initialParams: PropTypes.array, paramsType: PropTypes.string, initialValues: PropTypes.object, initialValidation: PropTypes.object, valueChanged: PropTypes.func, paramsChanged: PropTypes.func, onSubmit: PropTypes.func } export default CredentialInput ================================================ FILE: packages/ui/src/views/inputs/InputParameters.css ================================================ .editor__textarea { outline: 0; } .editor__textarea::placeholder { color: rgba(120, 120, 120, 0.5); } ================================================ FILE: packages/ui/src/views/inputs/InputParameters.js ================================================ import PropTypes from 'prop-types' import { forwardRef } from 'react' import { useSelector } from 'react-redux' // material-ui import { Box, Stack, Button, FormControl, OutlinedInput, Popper, TextField, Typography, Switch } from '@mui/material' import Autocomplete, { autocompleteClasses } from '@mui/material/Autocomplete' import { styled, useTheme } from '@mui/material/styles' // third party import lodash from 'lodash' import * as Yup from 'yup' import { Formik } from 'formik' import PerfectScrollbar from 'react-perfect-scrollbar' import DatePicker from 'react-datepicker' import 'react-datepicker/dist/react-datepicker.css' // project imports import useScriptRef from 'hooks/useScriptRef' import AnimateButton from 'ui-component/extended/AnimateButton' import ArrayInputParameters from './ArrayInputParameters' import OptionParamsResponse from './OptionParamsResponse' import AsyncSelectWrapper from './AsyncSelectWrapper' import { DarkCodeEditor } from 'ui-component/editor/DarkCodeEditor' import { LightCodeEditor } from 'ui-component/editor/LightCodeEditor' import { TooltipWithParser } from '../../ui-component/TooltipWithParser' import { StyledButton } from '../../ui-component/StyledButton' // icons import { IconPlus, IconUpload } from '@tabler/icons' // utils import { convertDateStringToDateObject, getFileName, getFolderName } from 'utils/genericHelper' //css import './InputParameters.css' const StyledPopper = styled(Popper)({ boxShadow: '0px 8px 10px -5px rgb(0 0 0 / 20%), 0px 16px 24px 2px rgb(0 0 0 / 14%), 0px 6px 30px 5px rgb(0 0 0 / 12%)', borderRadius: '10px', [`& .${autocompleteClasses.listbox}`]: { boxSizing: 'border-box', '& ul': { padding: 10, margin: 10, backgroundColor: 'red' } } }) const DateCustomInput = forwardRef(function DateCustomInput({ isDarkMode, value, onClick }, ref) { return ( ) }) DateCustomInput.propTypes = { isDarkMode: PropTypes.bool, value: PropTypes.string, onClick: PropTypes.func } // ==============================|| INPUT PARAMETERS ||============================== // const InputParameters = ({ params, paramsType, initialValues, nodeParamsValidation, nodeFlowData, valueChanged, onSubmit, setVariableSelectorState, onEditVariableDialogOpen, ...others }) => { const theme = useTheme() const customization = useSelector((state) => state.customization) const scriptedRef = useScriptRef() const onChanged = (values) => { const updateValues = values updateValues.submit = null valueChanged(updateValues, paramsType) } const onMouseUp = (e, inputName) => { const cursorPosition = e.target.selectionEnd const textBeforeCursorPosition = e.target.value.substring(0, cursorPosition) const textAfterCursorPosition = e.target.value.substring(cursorPosition, e.target.value.length) const path = `${paramsType}.${inputName}` const body = { textBeforeCursorPosition, textAfterCursorPosition, path, paramsType } setVariableSelectorState(true, body) } const onAddArrayItem = (values, arrayItems, arrayName) => { const updateValues = { ...values, [arrayName]: arrayItems } valueChanged(updateValues, paramsType) } const handleFolderUpload = (e, setFieldValue, values, inputName) => { setVariableSelectorState(false) if (!e.target.files) return const files = e.target.files const reader = new FileReader() function readFile(fileIndex, base64Array) { if (fileIndex >= files.length) { setFieldValue(inputName, JSON.stringify(base64Array)) const overwriteValues = { ...values, [inputName]: JSON.stringify(base64Array) } onChanged(overwriteValues) return } const file = files[fileIndex] reader.onload = (evt) => { if (!evt?.target?.result) { return } const { result } = evt.target const value = result + `,filepath:${file.webkitRelativePath}` base64Array.push(value) readFile(fileIndex + 1, lodash.cloneDeep(base64Array)) } reader.readAsDataURL(file) } readFile(0, []) } const handleFileUpload = (e, setFieldValue, values, inputName) => { setVariableSelectorState(false) if (!e.target.files) return const file = e.target.files[0] const { name } = file const reader = new FileReader() reader.onload = (evt) => { if (!evt?.target?.result) { return } const { result } = evt.target const value = result + `,filename:${name}` setFieldValue(inputName, value) const overwriteValues = { ...values, [inputName]: value } onChanged(overwriteValues) } reader.readAsDataURL(file) } const findMatchingOptions = (options = [], value) => options.find((option) => option.name === value) const getDefaultOptionValue = () => '' return ( <> { setVariableSelectorState(false) try { if (scriptedRef.current) { values.submit = true setStatus({ success: true }) setSubmitting(false) onSubmit(values, paramsType) } } catch (err) { console.error(err) if (scriptedRef.current) { setStatus({ success: false }) setErrors({ submit: err.message }) setSubmitting(false) } } }} > {({ errors, handleBlur, handleChange, handleSubmit, setFieldValue, isSubmitting, values }) => (
    {params.map((input) => { if (input.type === 'file' || input.type === 'folder') { const inputName = input.name return ( {input.label} {input.description && } {input.type === 'file' && ( {values[inputName] ? getFileName(values[inputName]) : 'Choose a file to upload'} )} {input.type === 'folder' && ( {values[inputName] ? getFolderName(values[inputName]) : 'Choose a folder to upload'} )} {errors[inputName] && ( *{errors[inputName]} )} ) } if (input.type === 'json' || input.type === 'code') { const inputName = input.name return ( {input.label} {input.description && } e.stopPropagation()} > {customization.isDarkMode ? ( { setFieldValue(inputName, code) }} placeholder={input.placeholder} type={input.type} onMouseUp={(e) => onMouseUp(e, inputName)} onBlur={(e) => { const overwriteValues = { ...values, [inputName]: e.target.value } onChanged(overwriteValues) onMouseUp(e, inputName) }} style={{ fontSize: '0.875rem', minHeight: '200px', width: '100%' }} /> ) : ( { setFieldValue(inputName, code) }} placeholder={input.placeholder} type={input.type} onMouseUp={(e) => onMouseUp(e, inputName)} onBlur={(e) => { const overwriteValues = { ...values, [inputName]: e.target.value } onChanged(overwriteValues) onMouseUp(e, inputName) }} style={{ fontSize: '0.875rem', minHeight: '200px', width: '100%' }} /> )} {errors[inputName] && ( *{errors[inputName]} )} ) } if (input.type === 'date') { const inputName = input.name return ( {input.label} {input.description && } } selected={convertDateStringToDateObject(values[inputName]) || null} showTimeInput isClearable timeInputLabel='Time:' dateFormat='MM/dd/yyyy h:mm aa' onChange={(date) => { const value = date ? date.toISOString() : null setVariableSelectorState(false) setFieldValue(inputName, value) const overwriteValues = { ...values, [inputName]: value } onChanged(overwriteValues) }} /> {errors[inputName] && ( *{errors[inputName]} )} ) } if (input.type === 'string' || input.type === 'password' || input.type === 'number') { const inputName = input.name return ( {input.label} {input.description && } {(input.type === 'string' || input.type === 'number') && ( )} { handleBlur(e) onChanged(values) onMouseUp(e, inputName) }} onMouseUp={(e) => onMouseUp(e, inputName)} onChange={handleChange} /> {errors[inputName] && ( *{errors[inputName]} )} ) } if (input.type === 'boolean') { const inputName = input.name return ( {input.label} {input.description && } { setVariableSelectorState(false) setFieldValue(inputName, event.target.checked) const overwriteValues = { ...values, [inputName]: event.target.checked } onChanged(overwriteValues) }} inputProps={{ 'aria-label': 'controlled' }} /> ) } if (input.type === 'asyncOptions') { const inputName = input.name return ( { const value = selection ? selection.name : '' setFieldValue(inputName, value) const overwriteValues = { ...values, [inputName]: value } onChanged(overwriteValues) }} onMenuOpen={() => setVariableSelectorState(false)} onSetError={() => { const value = '' setFieldValue(inputName, value) }} /> ) } if (input.type === 'options') { const inputName = input.name return ( {input.label} {input.description && } setVariableSelectorState(false)} options={input.options || []} value={findMatchingOptions(input.options, values[inputName]) || getDefaultOptionValue()} onChange={(e, selection) => { const value = selection ? selection.name : '' setFieldValue(inputName, value) const overwriteValues = { ...values, [inputName]: value } onChanged(overwriteValues) }} onInputChange={(e, value) => { if (!value) setFieldValue(inputName, '') }} onBlur={handleBlur} PopperComponent={StyledPopper} renderInput={(params) => ( )} renderOption={(props, option) => (
    {option.label} {option.description && ( {option.description} )}
    )} /> {errors[inputName] && ( *{errors[inputName]} )}
    ) } if (input.type === 'array') { const arrayParamItems = input.arrayParams const templateArray = input.array const inputName = input.name const arrayItemsValues = values[inputName] || [] return ( {input.label} {input.description && } { setFieldValue(inputName, updateInitialValues) }} onArrayInputBlur={(updateInitialValues) => { setFieldValue(inputName, updateInitialValues) const overwriteValues = { ...values, [inputName]: updateInitialValues } onChanged(overwriteValues) }} onArrayItemRemove={(updateInitialValues) => { setFieldValue(inputName, updateInitialValues) const overwriteValues = { ...values, [inputName]: updateInitialValues } onChanged(overwriteValues) }} onArrayItemMouseUp={(variableState, body) => { if (body) setVariableSelectorState(variableState, body) else setVariableSelectorState(variableState) }} onEditVariableDialogOpen={(arrayItemInput, arrayItemValues, arrayItemIndex) => { const arrayItemBody = { arrayItemInput, arrayItemValues, arrayItemIndex, initialValues: arrayItemsValues } onEditVariableDialogOpen(input, values, arrayItemBody) }} /> ) } return null })} 0} fullWidth size='large' type='submit' variant='contained' color='secondary' > Continue
    )}
    ) } InputParameters.propTypes = { params: PropTypes.array, paramsType: PropTypes.string, initialValues: PropTypes.object, nodeParamsValidation: PropTypes.object, nodeFlowData: PropTypes.object, valueChanged: PropTypes.func, onSubmit: PropTypes.func, setVariableSelectorState: PropTypes.func, onEditVariableDialogOpen: PropTypes.func } export default InputParameters ================================================ FILE: packages/ui/src/views/inputs/OptionParamsResponse.css ================================================ .params ul { line-height: 1.75em; } .params li { margin-bottom: 1em; } .inline { background-color: white; color: #4527a0; font-weight: 600; padding: 0.2em; padding-left: 0.4em; padding-right: 0.4em; border-radius: 0.5em; } ================================================ FILE: packages/ui/src/views/inputs/OptionParamsResponse.js ================================================ import PropTypes from 'prop-types' import { useSelector } from 'react-redux' // material-ui import { Box, Typography } from '@mui/material' import { useTheme } from '@mui/material/styles' // third party import ReactJson from 'react-json-view' // utils import { copyToClipboard } from 'utils/genericHelper' //css import './OptionParamsResponse.css' // ==============================|| OPTION PARAMS RESPONSE ||============================== // const OptionParamsResponse = ({ value, options }) => { const theme = useTheme() const customization = useSelector((state) => state.customization) const getSelectedValue = (value) => options.find((option) => option.name === value) const getSelectedOptionInputParams = (value) => { const selectedOption = options.find((option) => option.name === value) if (selectedOption) { return selectedOption.inputParameters || '' } return '' } const getSelectedOptionExampleParams = (value) => { const selectedOption = options.find((option) => option.name === value) if (selectedOption) { return selectedOption.exampleParameters || '' } return '' } const getSelectedOptionExampleResponse = (value) => { const selectedOption = options.find((option) => option.name === value) if (selectedOption) { return selectedOption.exampleResponse || '' } return '' } return ( <> {getSelectedValue(value) && getSelectedOptionInputParams(value) && ( Parameters
    )} {getSelectedValue(value) && getSelectedOptionExampleParams(value) && ( Example Parameters copyToClipboard(e)} /> )} {getSelectedValue(value) && getSelectedOptionExampleResponse(value) && ( Example Response copyToClipboard(e)} /> )} ) } OptionParamsResponse.propTypes = { value: PropTypes.string, options: PropTypes.array } export default OptionParamsResponse ================================================ FILE: packages/ui/src/views/output/OutputResponses.js ================================================ import { useState, useEffect } from 'react' import { useSelector } from 'react-redux' import PropTypes from 'prop-types' // material-ui import { Box, Button, Chip, CircularProgress, Stack, Typography, IconButton } from '@mui/material' import { useTheme } from '@mui/material/styles' // third party import ReactJson from 'react-json-view' import socketIOClient from 'socket.io-client' // project imports import AnimateButton from 'ui-component/extended/AnimateButton' import AttachmentDialog from 'ui-component/dialog/AttachmentDialog' import HTMLDialog from 'ui-component/dialog/HTMLDialog' import ExpandDataDialog from 'ui-component/dialog/ExpandDataDialog' import { StyledButton } from 'ui-component/StyledButton' // API import nodesApi from 'api/nodes' import webhookApi from 'api/webhooks' // Hooks import useApi from 'hooks/useApi' // icons import { IconExclamationMark, IconCopy, IconArrowUpRightCircle, IconX, IconArrowsMaximize } from '@tabler/icons' // const import { baseURL } from 'store/constant' // utils import { copyToClipboard } from 'utils/genericHelper' // ==============================|| OUTPUT RESPONSES ||============================== // const OutputResponses = ({ nodeId, nodeParamsType, nodeFlowData, nodes, edges, workflow, onSubmit }) => { const theme = useTheme() const customization = useSelector((state) => state.customization) const [outputResponse, setOutputResponse] = useState([]) const [errorResponse, setErrorResponse] = useState(null) const [nodeName, setNodeName] = useState(null) const [nodeType, setNodeType] = useState(null) const [nodeLabel, setNodeLabel] = useState(null) const [isTestNodeBtnDisabled, disableTestNodeBtn] = useState(true) const [testNodeLoading, setTestNodeLoading] = useState(null) const [showHTMLDialog, setShowHTMLDialog] = useState(false) const [HTMLDialogProps, setHTMLDialogProps] = useState({}) const [showAttachmentDialog, setShowAttachmentDialog] = useState(false) const [attachmentDialogProps, setAttachmentDialogProps] = useState({}) const [showExpandDialog, setShowExpandDialog] = useState(false) const [expandDialogProps, setExpandDialogProps] = useState({}) const [tunnelURL, setTunnelURL] = useState('') const testNodeApi = useApi(nodesApi.testNode) const getTunnelURLApi = useApi(webhookApi.getTunnelURL) const onTestNodeClick = (nodeType) => { /* If workflow is already deployed, stop it first to be safe. * Because it could cause throttled calls */ if (workflow.deployed) { setTestNodeLoading(false) alert('Testing trigger requires stopping workflow. Please stop workflow first') return } const testNodeBody = { nodes, edges, nodeId } try { setTestNodeLoading(true) if (nodeType === 'webhook') { const socket = socketIOClient(baseURL) socket.on('connect', async () => { testNodeBody.clientId = socket.id testNodeApi.request(nodeFlowData.name, testNodeBody) }) socket.on('testWebhookNodeResponse', (data) => { setOutputResponse(data) setTestNodeLoading(false) const formValues = { submit: true, needRetest: null, output: data } onSubmit(formValues, 'outputResponses') socket.disconnect() }) } else { testNodeApi.request(nodeFlowData.name, testNodeBody) } } catch (error) { setTestNodeLoading(false) setOutputResponse([]) setErrorResponse(error) console.error(error) } } const checkIfTestNodeValid = () => { const paramsTypes = nodeParamsType.filter((type) => type !== 'outputResponses') for (let i = 0; i < paramsTypes.length; i += 1) { const paramType = paramsTypes[i] if (!nodeFlowData[paramType] || !nodeFlowData[paramType].submit) { return true } } return false } const openAttachmentDialog = (outputResponse) => { const dialogProp = { title: 'Attachments', executionData: outputResponse } setAttachmentDialogProps(dialogProp) setShowAttachmentDialog(true) } const openHTMLDialog = (executionData) => { const dialogProp = { title: 'HTML', executionData } setHTMLDialogProps(dialogProp) setShowHTMLDialog(true) } const onExpandDialogClicked = (executionData) => { const dialogProp = { title: `Output Responses: ${nodeLabel} `, data: executionData } setExpandDialogProps(dialogProp) setShowExpandDialog(true) } useEffect(() => { if (nodeFlowData && nodeFlowData.outputResponses && nodeFlowData.outputResponses.output) { setOutputResponse(nodeFlowData.outputResponses.output) } else { setOutputResponse([]) } disableTestNodeBtn(checkIfTestNodeValid()) // eslint-disable-next-line react-hooks/exhaustive-deps }, [nodeFlowData, nodeParamsType]) useEffect(() => { if (nodes && nodeId) { const selectedNode = nodes.find((nd) => nd.id === nodeId) if (selectedNode) { setNodeName(selectedNode.data.name) setNodeType(selectedNode.data.type) setNodeLabel(selectedNode.data.label) } } }, [nodes, nodeId]) // Test node successful useEffect(() => { if (testNodeApi.data && nodeType && nodeType !== 'webhook') { const testNodeData = testNodeApi.data setOutputResponse(testNodeData) setErrorResponse(null) const formValues = { submit: true, needRetest: null, output: testNodeData } onSubmit(formValues, 'outputResponses') } // eslint-disable-next-line react-hooks/exhaustive-deps }, [testNodeApi.data]) useEffect(() => { getTunnelURLApi.request() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) useEffect(() => { if (getTunnelURLApi.data) { setTunnelURL(getTunnelURLApi.data) } }, [getTunnelURLApi.data]) // Test node error useEffect(() => { if (testNodeApi.error && nodeType && nodeType !== 'webhook') { let errorMessage = 'Unexpected Error.' if (testNodeApi.error.response && testNodeApi.error.response.data) { errorMessage = testNodeApi.error.response.data } else if (testNodeApi.error.message) { errorMessage = testNodeApi.error.message } setErrorResponse(errorMessage) setOutputResponse([]) const formValues = { submit: null, needRetest: null, output: [] } onSubmit(formValues, 'outputResponses') } // eslint-disable-next-line react-hooks/exhaustive-deps }, [testNodeApi.error]) // Test node loading useEffect(() => { if (nodeType && nodeType !== 'webhook') setTestNodeLoading(testNodeApi.loading) // eslint-disable-next-line react-hooks/exhaustive-deps }, [testNodeApi.loading]) return ( <> {nodeFlowData && nodeFlowData.outputResponses && nodeFlowData.outputResponses.needRetest && ( } label='Retest the node for updated parameters' color='warning' /> )} {nodeName && (nodeName === 'webhook' || nodeName === 'chainLinkFunctionWebhook') && ( Local URL: {`${baseURL}/api/v1/webhook/${nodeFlowData.webhookEndpoint}`} {tunnelURL && (
    Tunnel URL: {`${tunnelURL}api/v1/webhook/${nodeFlowData.webhookEndpoint}`}
    )}
    )} {errorResponse && ( } label='Error' color='error' />
    {errorResponse}
    )} copyToClipboard(e)} /> onExpandDialogClicked(outputResponse)} >
    {outputResponse.map((respObj, respObjIndex) => (
    {respObj.html && ( HTML )} {respObj.html && (
    )} {respObj.html && ( openHTMLDialog(outputResponse)} > View HTML )} {respObj.attachments && ( Attachments )} {respObj.attachments && respObj.attachments.map((attachment, attchIndex) => (
    Item {respObjIndex} |{' '} {attachment.filename ? attachment.filename : `Attachment ${attchIndex}`}
    ))}
    ))}
    onTestNodeClick(nodeType)} > Test Node {testNodeLoading && ( )} setShowAttachmentDialog(false)} > setShowHTMLDialog(false)}> setShowExpandDialog(false)} > ) } OutputResponses.propTypes = { nodeId: PropTypes.string, nodeParamsType: PropTypes.array, nodeFlowData: PropTypes.object, nodes: PropTypes.array, edges: PropTypes.array, workflow: PropTypes.object, onSubmit: PropTypes.func } export default OutputResponses ================================================ FILE: packages/ui/src/views/settings/index.js ================================================ import { useState, useEffect } from 'react' import PropTypes from 'prop-types' // material-ui import { useTheme } from '@mui/material/styles' import { Box, List, Paper, Popper } from '@mui/material' // third-party import PerfectScrollbar from 'react-perfect-scrollbar' // project imports import MainCard from 'ui-component/cards/MainCard' import Transitions from 'ui-component/extended/Transitions' import NavItem from 'layout/MainLayout/Sidebar/MenuList/NavItem' import settings from 'menu-items/settings' // ==============================|| SETTINGS ||============================== // const Settings = ({ workflow, isSettingsOpen, anchorEl, onSettingsItemClick, onUploadFile }) => { const theme = useTheme() const [settingsMenu, setSettingsMenu] = useState([]) const [open, setOpen] = useState(false) useEffect(() => { if (workflow && !workflow.shortId) { const settingsMenu = settings.children.filter((menu) => menu.id === 'loadWorkflow') setSettingsMenu(settingsMenu) } else if (workflow && workflow.shortId) { const settingsMenu = settings.children setSettingsMenu(settingsMenu) } }, [workflow]) useEffect(() => { setOpen(isSettingsOpen) }, [isSettingsOpen]) // settings list items const items = settingsMenu.map((menu) => { return ( onSettingsItemClick(id)} onUploadFile={onUploadFile} /> ) }) return ( <> {({ TransitionProps }) => ( {items} )} ) } Settings.propTypes = { workflow: PropTypes.object, isSettingsOpen: PropTypes.bool, anchorEl: PropTypes.any, onSettingsItemClick: PropTypes.func, onUploadFile: PropTypes.func } export default Settings ================================================ FILE: packages/ui/src/views/wallets/WalletDialog.js ================================================ import { createPortal } from 'react-dom' import PropTypes from 'prop-types' import { useState, useEffect } from 'react' import { useDispatch } from 'react-redux' import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' import { Avatar, Accordion, AccordionSummary, AccordionDetails, Box, Divider, Typography, Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, IconButton } from '@mui/material' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import { useTheme } from '@mui/material/styles' // third-party import * as Yup from 'yup' import lodash from 'lodash' // project imports import InputParameters from 'views/inputs/InputParameters' import CredentialInput from 'views/inputs/CredentialInput' import EditVariableDialog from 'ui-component/dialog/EditVariableDialog' import { StyledButton } from 'ui-component/StyledButton' // Icons import { IconCheck, IconX, IconArrowUpRightCircle, IconCopy, IconKey } from '@tabler/icons' // API import walletsApi from 'api/wallets' // Hooks import useApi from 'hooks/useApi' // Const import { wallet_details, networkExplorers, privateKeyField } from 'store/constant' // utils import { handleCredentialParams, initializeNodeData } from 'utils/genericHelper' import useNotifier from 'utils/useNotifier' const WalletDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const portalElement = document.getElementById('portal') const theme = useTheme() const dispatch = useDispatch() // ==============================|| Snackbar ||============================== // useNotifier() const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) const [walletDetails, setWalletDetails] = useState(wallet_details) const [walletData, setWalletData] = useState({}) const [walletParams, setWalletParams] = useState([]) const [walletValues, setWalletValues] = useState({}) const [walletValidation, setWalletValidation] = useState({}) const [walletCredential, setWalletCredential] = useState({}) const [expanded, setExpanded] = useState(false) const [isReadyToAdd, setIsReadyToAdd] = useState(false) const [isEditVariableDialogOpen, setEditVariableDialog] = useState(false) const [editVariableDialogProps, setEditVariableDialogProps] = useState({}) const walletParamsType = ['networks', 'credentials', 'walletInfo'] const getSpecificWalletApi = useApi(walletsApi.getSpecificWallet) const getWalletCredentialApi = useApi(walletsApi.getWalletCredential) const handleAccordionChange = (expanded) => (event, isExpanded) => { setExpanded(isExpanded ? expanded : false) } const reset = () => { setWalletData({}) setWalletParams([]) setWalletValues({}) setWalletValidation({}) setWalletCredential({}) setIsReadyToAdd(false) setExpanded(false) } const checkIsReadyToAdd = (walletData) => { for (let i = 0; i < walletParamsType.length; i += 1) { const paramType = walletParamsType[i] if (!walletData[paramType] || !walletData[paramType].submit) { setIsReadyToAdd(false) return } } setIsReadyToAdd(true) } const onEditVariableDialogOpen = (input, values, arrayItemBody) => { const dialogProps = { input, values, arrayItemBody, cancelButtonName: 'Cancel', confirmButtonName: 'Save', hideVariables: true } setEditVariableDialogProps(dialogProps) setEditVariableDialog(true) } const addNewWallet = async (type) => { const createNewWalletBody = { network: walletData.networks.network, name: walletData.walletInfo.name, providerCredential: JSON.stringify(walletData.credentials) } if (type === 'IMPORT') createNewWalletBody.privateKey = walletData.walletInfo.privateKey try { const createResp = await walletsApi.createNewWallet(createNewWalletBody) if (createResp.data) { enqueueSnackbar({ message: 'New wallet added', options: { key: new Date().getTime() + Math.random(), variant: 'success', action: (key) => ( ) } }) onConfirm() } } catch (error) { const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` enqueueSnackbar({ message: `Failed to add new wallet: ${errorData}`, options: { key: new Date().getTime() + Math.random(), variant: 'error', persist: true, action: (key) => ( ) } }) onCancel() } } const saveWallet = async () => { const saveWalletBody = { network: walletData.networks.network, name: walletData.walletInfo.name, providerCredential: JSON.stringify(walletData.credentials) } try { const saveResp = await walletsApi.updateWallet(dialogProps.id, saveWalletBody) if (saveResp.data) { enqueueSnackbar({ message: 'Wallet saved', options: { key: new Date().getTime() + Math.random(), variant: 'success', action: (key) => ( ) } }) onConfirm() } } catch (error) { const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` enqueueSnackbar({ message: `Failed to save wallet: ${errorData}`, options: { key: new Date().getTime() + Math.random(), variant: 'error', persist: true, action: (key) => ( ) } }) onCancel() } } const deleteWallet = async () => { try { const deleteResp = await walletsApi.deleteWallet(dialogProps.id) if (deleteResp.data) { enqueueSnackbar({ message: 'Wallet deleted', options: { key: new Date().getTime() + Math.random(), variant: 'success', action: (key) => ( ) } }) onConfirm() } } catch (error) { const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` enqueueSnackbar({ message: `Failed to delete wallet: ${errorData}`, options: { key: new Date().getTime() + Math.random(), variant: 'error', persist: true, action: (key) => ( ) } }) onCancel() } } const valueChanged = (formValues, paramsType) => { const updateWalletData = { ...walletData, [paramsType]: formValues } const index = walletParamsType.indexOf(paramsType) if (index >= 0 && index !== walletParamsType.length - 1) { for (let i = index + 1; i < walletParamsType.length; i += 1) { const paramType = walletParamsType[i] if (updateWalletData[paramType]) { updateWalletData[paramType].submit = null } } } setWalletData(updateWalletData) } const paramsChanged = (formParams, paramsType) => { // Because formParams options can be changed due to show hide options, // To avoid that, replace with original details options const credentialMethodParam = formParams.find((param) => param.name === 'credentialMethod') const credentialMethodParamIndex = formParams.findIndex((param) => param.name === 'credentialMethod') if (credentialMethodParam !== undefined) { const originalParam = walletDetails[paramsType].find((param) => param.name === 'credentialMethod') if (originalParam !== undefined) { formParams[credentialMethodParamIndex]['options'] = originalParam.options } } const updateWalletDetails = { ...walletDetails, [paramsType]: formParams } setWalletDetails(updateWalletDetails) } const onSubmit = async (formValues, paramsType) => { const updateWalletData = { ...walletData, [paramsType]: formValues } setWalletData(updateWalletData) const index = walletParamsType.indexOf(paramsType) if (index >= 0 && index !== walletParamsType.length - 1) { setExpanded(walletParamsType[index + 1]) } else if (index === walletParamsType.length - 1) { setExpanded(false) } checkIsReadyToAdd(updateWalletData) } const showHideOptions = (displayType, options) => { let returnOptions = options const toBeDeleteOptions = [] for (let i = 0; i < returnOptions.length; i += 1) { const option = returnOptions[i] const displayOptions = option[displayType] if (displayOptions) { Object.keys(displayOptions).forEach((path) => { const comparisonValue = displayOptions[path] const groundValue = lodash.get(walletData, path, '') if (Array.isArray(comparisonValue)) { if (displayType === 'show' && !comparisonValue.includes(groundValue)) { toBeDeleteOptions.push(option) } if (displayType === 'hide' && comparisonValue.includes(groundValue)) { toBeDeleteOptions.push(option) } } }) } } for (let i = 0; i < toBeDeleteOptions.length; i += 1) { returnOptions = returnOptions.filter((opt) => JSON.stringify(opt) !== JSON.stringify(toBeDeleteOptions[i])) } return returnOptions } const displayOptions = (params) => { let clonedParams = params for (let i = 0; i < clonedParams.length; i += 1) { const input = clonedParams[i] if (input.type === 'options') { input.options = showHideOptions('show', input.options) input.options = showHideOptions('hide', input.options) } } return clonedParams } const setYupValidation = (params) => { const validationSchema = {} for (let i = 0; i < params.length; i += 1) { const input = params[i] if (input.type === 'string' && !input.optional) { validationSchema[input.name] = Yup.string().required(`${input.label} is required. Type: ${input.type}`) } else if (input.type === 'number' && !input.optional) { validationSchema[input.name] = Yup.number().required(`${input.label} is required. Type: ${input.type}`) } else if ((input.type === 'options' || input.type === 'asyncOptions') && !input.optional) { validationSchema[input.name] = Yup.string().required(`${input.label} is required. Type: ${input.type}`) } } return validationSchema } const initializeFormValuesAndParams = (paramsType) => { const initialValues = {} let walletParams = displayOptions(lodash.cloneDeep(walletDetails[paramsType] || [])) walletParams = handleCredentialParams(walletParams, paramsType, walletDetails[paramsType], walletData) if (dialogProps.type === 'IMPORT' && paramsType === 'walletInfo') { walletParams.push(...privateKeyField) } for (let i = 0; i < walletParams.length; i += 1) { const input = walletParams[i] // Load from walletData values if (paramsType in walletData && input.name in walletData[paramsType]) { initialValues[input.name] = walletData[paramsType][input.name] // Check if option value is still available from the list of options if (input.type === 'options') { const optionVal = input.options.find((option) => option.name === initialValues[input.name]) if (!optionVal) delete initialValues[input.name] } } else { // Load from walletParams default values initialValues[input.name] = input.default || '' } } initialValues.submit = null setWalletValues(initialValues) setWalletValidation(setYupValidation(walletParams)) setWalletParams(walletParams) } const transformWalletResponse = (walletResponseData, walletDetails) => { const walletData = { networks: {}, credentials: {}, walletInfo: {} } if (walletResponseData) { walletData.networks = { network: walletResponseData.network, submit: true } walletData.walletInfo = { ...walletResponseData, submit: true } if (walletResponseData.providerCredential) { try { walletData.credentials = JSON.parse(walletResponseData.providerCredential) } catch (e) { console.error(e) } } } else { walletData.networks = initializeNodeData(walletDetails.networks) walletData.credentials = initializeNodeData(walletDetails.credentials) walletData.walletInfo = initializeNodeData(walletDetails.walletInfo) } return walletData } // Get Wallet Details from API useEffect(() => { if (getSpecificWalletApi.data) { const walletResponseData = getSpecificWalletApi.data setWalletData(transformWalletResponse(walletResponseData)) setExpanded('networks') } }, [getSpecificWalletApi.data]) // Get Wallet Credential from API useEffect(() => { if (getWalletCredentialApi.data) { const walletCredResponseData = getWalletCredentialApi.data setWalletCredential(walletCredResponseData) } }, [getWalletCredentialApi.data]) // Initialization useEffect(() => { if (show && (dialogProps.type === 'ADD' || dialogProps.type === 'IMPORT')) { reset() setWalletData(transformWalletResponse(null, walletDetails)) setExpanded('networks') } else if (show && dialogProps.type === 'EDIT' && dialogProps.id) { reset() getSpecificWalletApi.request(dialogProps.id) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [show, dialogProps]) // Initialize Parameters Initial Values & Validation useEffect(() => { if (walletDetails && walletData && expanded) { initializeFormValuesAndParams(expanded) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [walletDetails, walletData, expanded]) const component = show ? ( {dialogProps.title} {walletData && walletData.walletInfo && walletData.walletInfo.address && dialogProps.type === 'EDIT' && ( BALANCE {walletData.walletInfo.balance} ADDRESS {walletData.walletInfo.address} navigator.clipboard.writeText(walletData.walletInfo.address)} > window.open( `${networkExplorers[walletData.networks.network]}/address/${walletData.walletInfo.address}`, '_blank' ) } > {walletCredential && walletCredential.privateKey && ( <> PRIVATE KEY {walletCredential.privateKey} navigator.clipboard.writeText(walletCredential.privateKey)} > )} {walletCredential && walletCredential.mnemonic && ( <> mnemonic {walletCredential.mnemonic} navigator.clipboard.writeText(walletCredential.mnemonic)} > )} {!Object.keys(walletCredential).length && ( } onClick={() => getWalletCredentialApi.request(dialogProps.id)} > View PrivateKey and Mnemonic )} )} {/* networks */} } aria-controls='networks-content' id='networks-header'> Networks {walletData && walletData.networks && walletData.networks.submit && ( )} null} onEditVariableDialogOpen={onEditVariableDialogOpen} /> {/* credentials */} } aria-controls='credentials-content' id='credentials-header'> Credentials {walletData && walletData.credentials && walletData.credentials.submit && ( )} {/* walletInfo */} } aria-controls='walletInfo-content' id='walletInfo-header'> Wallet Details {walletData && walletData.walletInfo && walletData.walletInfo.submit && ( )} null} onEditVariableDialogOpen={onEditVariableDialogOpen} /> setEditVariableDialog(false)} onConfirm={(updateValues) => { valueChanged(updateValues, expanded) setEditVariableDialog(false) }} /> {dialogProps.type === 'EDIT' && ( deleteWallet()}> Delete )} dialogProps.type === 'ADD' || dialogProps.type === 'IMPORT' ? addNewWallet(dialogProps.type) : saveWallet() } > {dialogProps.confirmButtonName} ) : null return createPortal(component, portalElement) } WalletDialog.propTypes = { show: PropTypes.bool, dialogProps: PropTypes.object, onCancel: PropTypes.func, onConfirm: PropTypes.func } export default WalletDialog ================================================ FILE: packages/ui/src/views/wallets/index.js ================================================ import { useEffect, useState } from 'react' import { useSelector } from 'react-redux' // material-ui import { Grid, Box, Stack } from '@mui/material' import { useTheme } from '@mui/material/styles' // project imports import MainCard from 'ui-component/cards/MainCard' import ItemCard from 'ui-component/cards/ItemCard' import WalletDialog from './WalletDialog' import WalletEmptySVG from 'assets/images/wallet_empty.svg' import { TooltipWithParser } from 'ui-component/TooltipWithParser' import { StyledButton } from 'ui-component/StyledButton' // const import { gridSpacing } from 'store/constant' // API import walletsApi from 'api/wallets' // Hooks import useApi from 'hooks/useApi' // ==============================|| WALLETS ||============================== // const Wallets = () => { const theme = useTheme() const customization = useSelector((state) => state.customization) const [isLoading, setLoading] = useState(true) const [showDialog, setShowDialog] = useState(false) const [dialogProps, setDialogProps] = useState({}) const getAllWalletsApi = useApi(walletsApi.getAllWallets) const addNew = () => { const dialogProp = { title: 'Add New Wallet', type: 'ADD', cancelButtonName: 'Cancel', confirmButtonName: 'Add' } setDialogProps(dialogProp) setShowDialog(true) } const importNew = () => { const dialogProp = { title: 'Import Wallet', type: 'IMPORT', cancelButtonName: 'Cancel', confirmButtonName: 'IMPORT' } setDialogProps(dialogProp) setShowDialog(true) } const edit = (id) => { const dialogProp = { title: 'Edit Wallet', type: 'EDIT', cancelButtonName: 'Cancel', confirmButtonName: 'Save', id } setDialogProps(dialogProp) setShowDialog(true) } const onConfirm = () => { setShowDialog(false) getAllWalletsApi.request() } useEffect(() => { getAllWalletsApi.request() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) useEffect(() => { setLoading(getAllWalletsApi.loading) }, [getAllWalletsApi.loading]) return ( <>

    Wallets 

    Add New Import Wallet
    {!isLoading && getAllWalletsApi.data && getAllWalletsApi.data.map((data, index) => ( edit(data._id)} data={data} /> ))} {!isLoading && (!getAllWalletsApi.data || getAllWalletsApi.data.length === 0) && ( WalletEmptySVG
    No Wallets Yet
    )}
    setShowDialog(false)} onConfirm={onConfirm} > ) } export default Wallets ================================================ FILE: packages/ui/src/views/workflows/index.js ================================================ import { useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' import { useDispatch, useSelector } from 'react-redux' // material-ui import { Grid, Box, Stack, Tooltip, Switch } from '@mui/material' import { useTheme, styled } from '@mui/material/styles' // project imports import MainCard from 'ui-component/cards/MainCard' import ItemCard from 'ui-component/cards/ItemCard' import { gridSpacing } from 'store/constant' import WorkflowEmptySVG from 'assets/images/workflow_empty.svg' import { SET_LAYOUT } from 'store/actions' import { StyledButton } from 'ui-component/StyledButton' // API import workflowsApi from 'api/workflows' // Hooks import useApi from 'hooks/useApi' // const import { baseURL } from 'store/constant' const MaterialUISwitch = styled(Switch)(({ theme }) => ({ width: 62, height: 34, padding: 7, '& .MuiSwitch-switchBase': { margin: 1, padding: 0, transform: 'translateX(6px)', '&.Mui-checked': { color: '#fff', transform: 'translateX(22px)', '& .MuiSwitch-thumb:before': { backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='icon icon-tabler icon-tabler-switch-vertical' width='24' height='24' viewBox='0 0 24 24' stroke-width='2' stroke='%23fff' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'%3E%3C/path%3E%3Cpath d='M3 8l4 -4l4 4'%3E%3C/path%3E%3Cpath d='M7 4l0 9'%3E%3C/path%3E%3Cpath d='M13 16l4 4l4 -4'%3E%3C/path%3E%3Cpath d='M17 10l0 10'%3E%3C/path%3E%3C/svg%3E")` }, '& + .MuiSwitch-track': { opacity: 1, backgroundColor: theme.palette.mode === 'dark' ? '#8796A5' : '#aab4be' } } }, '& .MuiSwitch-thumb': { backgroundColor: theme.palette.mode === 'dark' ? '#003892' : '#001e3c', width: 32, height: 32, '&:before': { content: "''", position: 'absolute', width: '100%', height: '100%', left: 0, top: 0, backgroundRepeat: 'no-repeat', backgroundPosition: 'center', backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='icon icon-tabler icon-tabler-switch-horizontal' width='24' height='24' viewBox='0 0 24 24' stroke-width='2' stroke='%23fff' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'%3E%3C/path%3E%3Cpath d='M16 3l4 4l-4 4'%3E%3C/path%3E%3Cpath d='M10 7l10 0'%3E%3C/path%3E%3Cpath d='M8 13l-4 4l4 4'%3E%3C/path%3E%3Cpath d='M4 17l9 0'%3E%3C/path%3E%3C/svg%3E")` } }, '& .MuiSwitch-track': { opacity: 1, backgroundColor: theme.palette.mode === 'dark' ? '#8796A5' : '#aab4be', borderRadius: 20 / 2 } })) // ==============================|| WORKFLOWS ||============================== // const Workflows = () => { const navigate = useNavigate() const theme = useTheme() const dispatch = useDispatch() const customization = useSelector((state) => state.customization) const [isLoading, setLoading] = useState(true) const [images, setImages] = useState({}) const [isHorizontal, setIsHorizontal] = useState(customization.isHorizontal) const getAllWorkflowsApi = useApi(workflowsApi.getAllWorkflows) const addNew = () => { navigate('/canvas') } const goToCanvas = (selectedWorkflow) => { navigate(`/canvas/${selectedWorkflow.shortId}`) } useEffect(() => { getAllWorkflowsApi.request() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) useEffect(() => { setLoading(getAllWorkflowsApi.loading) }, [getAllWorkflowsApi.loading]) useEffect(() => { if (getAllWorkflowsApi.data) { try { const workflows = getAllWorkflowsApi.data const images = {} for (let i = 0; i < workflows.length; i += 1) { const flowDataStr = workflows[i].flowData const flowData = JSON.parse(flowDataStr) const nodes = flowData.nodes || [] images[workflows[i].shortId] = [] for (let j = 0; j < nodes.length; j += 1) { const imageSrc = `${baseURL}/api/v1/node-icon/${nodes[j].data.name}` if (!images[workflows[i].shortId].includes(imageSrc)) { images[workflows[i].shortId].push(imageSrc) } } } setImages(images) } catch (e) { console.error(e) } } }, [getAllWorkflowsApi.data]) const handleSwapLayout = () => { dispatch({ type: SET_LAYOUT, isHorizontal: !isHorizontal }) setIsHorizontal((isHorizontal) => !isHorizontal) localStorage.setItem('isHorizontal', !isHorizontal) } return (

    Workflows

    Add New
    {!isLoading && getAllWorkflowsApi.data && getAllWorkflowsApi.data.map((data, index) => ( goToCanvas(data)} data={data} images={images[data.shortId]} /> ))} {!isLoading && (!getAllWorkflowsApi.data || getAllWorkflowsApi.data.length === 0) && ( WorkflowEmptySVG
    No Workflows Yet
    )}
    ) } export default Workflows ================================================ FILE: turbo.json ================================================ { "$schema": "https://turbo.build/schema.json", "pipeline": { "build": { "dependsOn": ["^build"], "outputs": ["dist/**"] }, "test": {}, "dev": { "cache": false } } }