diff --git a/extensions/cactus-plugin-htlc-coordinator/README.md b/extensions/cactus-plugin-htlc-coordinator/README.md new file mode 100644 index 00000000000..326f9cd3b1b --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/README.md @@ -0,0 +1,104 @@ +# `@hyperledger/cactus-plugin-htlc-coordinator` + +Allows Cactus nodes to interact beetwen diferent networks. Using this we can perform: +* Instantiate an existing HTLC plugin, also the own HTLC and the counterparty HTLC. +* Interact with the HTLC, deploying, checking and withdrawing the funds. +## Summary + + - [Usage](#usage) + - [Development](#development) + - [Getting Started](#getting-started) + - [Flow](#flow) + - [Contributing](#contributing) + - [License](#license) + - [Acknowledgments](#acknowledgments) + +## Usage + +To use this import public-api and create new **PluginFactoryHTLCCoordinator*. Then use it to create a HTLC Coordinator. +```typescript + const factoryHTLC = new PluginFactoryHTLCCoordinator({ + pluginImportType: PluginImportType.Local, + }); + const pluginHTLCCoordinator = await factoryHTLC.create(pluginOptions); +``` +You can make calls through the htlc coordinator to the plugin API: + +```typescript +async ownHTLC(ownHTLCRequest: OwnHTLCRequest): Promise; +async counterpartyHTLC(counterpartyHTLCRequest: CounterpartyHTLCRequest): Promise; +async withdrawCounterparty(withdrawCounterpartyRequest: WithdrawCounterpartyRequest): Promise; +``` + +Call example to create an ownHTLC and instantiate a HTLC contract: +```typescript + const ownHTLCRequest: OwnHTLCRequest = { + htlcPackage: HtlcPackage.BesuErc20, + connectorInstanceId, + keychainId, + constructorArgs: [], + web3SigningCredential, + inputAmount: 10, + outputAmount: 1, + expiration, + hashLock, + tokenAddress, + receiver, + outputNetwork: "BTC", + outputAddress: "1AcVYm7M3kkJQH28FXAvyBFQzFRL6xPKu8", + gas: estimatedGas, + }; + const response = await coordinator.ownCoordinator(ownHTLCRequest); +}); +``` +The field "htlcPackage" can have the following values: +```typescript + enum HtlcPackage { + Besu = 'BESU', + BesuErc20 = 'BESU_ERC20' + } +``` + +## Development + +### Getting Started + +Clone the git repository on your local machine. Follow these instructions that will get you a copy of the project up and running on +your local machine for development and testing purposes. + +#### Prerequisites + +In the root of the project to install the dependencies execute the command: +```sh +yarn run configure +``` + +#### Compiling + +In the project root folder, run this command to compile the plugin and create the dist directory: +```sh +yarn run watch +``` + +### Flow + +#### Alice flow + +The [Alice diagram](docs/flow/htlc-coordinator-alice-flow.md, "Alice Flow") shows the sequence diagram of a complete flow for a participant who wants exchange funds with another participant. She doesn't start the withdraw flow becuase she doesn't know the secret to withdraw them. + +#### Bob flow + +The next [Bob diagram](docs/flow/htlc-coordinator-bob-flow.md, "Bob Flow") +shows the sequence diagram of a complete flow for a participant who wants exchange funds with another participant, but he knows the secret and starts the flow to withdraw the funds. + + +## Contributing + +We welcome contributions to Hyperledger Cactus in many forms, and there’s always plenty to do! + +Please review [CONTIRBUTING.md](../../CONTRIBUTING.md) to get started. + +## License + +This distribution is published under the Apache License Version 2.0 found in the [LICENSE](../../LICENSE) file. + diff --git a/extensions/cactus-plugin-htlc-coordinator/docs/flow/htlc-coordinator-alice-flow.md b/extensions/cactus-plugin-htlc-coordinator/docs/flow/htlc-coordinator-alice-flow.md new file mode 100644 index 00000000000..847e0664cc8 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/docs/flow/htlc-coordinator-alice-flow.md @@ -0,0 +1,69 @@ +```mermaid +sequenceDiagram +autonumber + participant Alice + participant HTLC Coordinator Alice + participant HTLC Plugin Besu + participant HTLC Plugin Counterparty + participant HTLC Contract Alice + participant HTLC Contract Counterparty + + + + Note over Alice: Bob has deployed his contract and share the information with Alice + Alice ->> HTLC Coordinator Alice: newCounterparty(HTLC Plugin Counterparty) + activate HTLC Coordinator Alice + HTLC Coordinator Alice ->> HTLC Plugin Counterparty: newInstance() + activate HTLC Plugin Counterparty + HTLC Plugin Counterparty-->> HTLC Coordinator Alice: plugin instance + deactivate HTLC Plugin Counterparty + HTLC Coordinator Alice ->> HTLC Plugin Counterparty: getSingleStatus() + activate HTLC Plugin Counterparty + HTLC Plugin Counterparty -->> HTLC Contract Counterparty: getSingleStatus() + activate HTLC Contract Counterparty + HTLC Contract Counterparty -->> HTLC Plugin Counterparty: ok + deactivate HTLC Contract Counterparty + HTLC Plugin Counterparty -->> HTLC Coordinator Alice: ok + deactivate HTLC Plugin Counterparty + HTLC Coordinator Alice ->> Alice: HTLC Plugin Counterparty instance + deactivate HTLC Coordinator Alice + Note over Alice: Alice has validated that the Bob contract information is correct + Alice ->> HTLC Coordinator Alice: newCoordinator(HTLC Plugin Besu) + activate HTLC Coordinator Alice + HTLC Coordinator Alice ->> HTLC Plugin Besu: newInstance() + activate HTLC Plugin Besu + HTLC Plugin Besu -->> HTLC Coordinator Alice: plugin instance + deactivate HTLC Plugin Besu + HTLC Coordinator Alice ->> HTLC Plugin Besu: newContract() + activate HTLC Plugin Besu + HTLC Plugin Besu ->> HTLC Contract Alice: deployContract() + activate HTLC Contract Alice + HTLC Contract Alice -->> HTLC Plugin Besu: ok + deactivate HTLC Contract Alice + HTLC Plugin Besu -->> HTLC Coordinator Alice: ok + HTLC Plugin Besu -->> HTLC Plugin Besu: Listening contract events + + HTLC Coordinator Alice ->> Alice: Alice HTLC Coordinator instance + deactivate HTLC Coordinator Alice + Note over Alice: Alice send all information about her contract to Bob + Note over Alice: Her counterparty invoke the withdraw function + HTLC Plugin Besu -->> HTLC Coordinator Alice: Event - CounterParty withdraw + deactivate HTLC Plugin Besu + activate HTLC Coordinator Alice + HTLC Coordinator Alice -->> Alice: Counterparty withdraw notification + deactivate HTLC Coordinator Alice + Note over Alice: Alice can call the withdrawCounterparty because now she can see the secret + Alice ->> HTLC Coordinator Alice: WithdrawCounterparty() + activate HTLC Coordinator Alice + HTLC Coordinator Alice ->> HTLC Plugin Counterparty: WithdrawCounterparty() + activate HTLC Plugin Counterparty + HTLC Plugin Counterparty ->> HTLC Contract Counterparty: withdraw() + activate HTLC Contract Counterparty + HTLC Contract Counterparty -->> HTLC Plugin Counterparty: ok + deactivate HTLC Contract Counterparty + HTLC Plugin Counterparty -->> HTLC Coordinator Alice: ok + deactivate HTLC Plugin Counterparty + HTLC Coordinator Alice -->> Alice: ok + deactivate HTLC Coordinator Alice + +``` \ No newline at end of file diff --git a/extensions/cactus-plugin-htlc-coordinator/docs/flow/htlc-coordinator-bob-flow.md b/extensions/cactus-plugin-htlc-coordinator/docs/flow/htlc-coordinator-bob-flow.md new file mode 100644 index 00000000000..2bd00d05959 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/docs/flow/htlc-coordinator-bob-flow.md @@ -0,0 +1,68 @@ +```mermaid +sequenceDiagram +autonumber + participant Bob + participant HTLC Coordinator Bob + participant HTLC Plugin Besu ERC20 + participant HTLC Plugin Counterparty + participant HTLC Contract Bob + participant HTLC Contract Counterparty + + Note over Bob: We assumped that the offer take place on the front end + Note over Bob: Bob is the first to deploy the HTLC contract + Bob ->> HTLC Coordinator Bob: newCoordinator(HTLC Plugin Besu) + activate HTLC Coordinator Bob + HTLC Coordinator Bob ->> HTLC Plugin Besu ERC20:newInstance() + activate HTLC Plugin Besu ERC20 + HTLC Plugin Besu ERC20-->> HTLC Coordinator Bob: plugin instance + deactivate HTLC Plugin Besu ERC20 + HTLC Coordinator Bob ->> HTLC Plugin Besu ERC20:newContract() + activate HTLC Plugin Besu ERC20 + HTLC Plugin Besu ERC20->> HTLC Contract Bob: deployContract() + activate HTLC Contract Bob + HTLC Contract Bob -->> HTLC Plugin Besu ERC20:ok + deactivate HTLC Contract Bob + HTLC Plugin Besu ERC20-->> HTLC Coordinator Bob: ok + HTLC Plugin Besu ERC20-->> HTLC Plugin Besu ERC20:Listening contract events + + HTLC Coordinator Bob ->> Bob: Bob HTLC Coordinator instance + deactivate HTLC Coordinator Bob + Note over Bob: Bob share with Alice all the information about his contract + Bob ->> HTLC Coordinator Bob: newCounterparty(HTLC Plugin Counterparty) + activate HTLC Coordinator Bob + HTLC Coordinator Bob ->> HTLC Plugin Counterparty: newInstance() + activate HTLC Plugin Counterparty + HTLC Plugin Counterparty-->> HTLC Coordinator Bob: plugin instance + deactivate HTLC Plugin Counterparty + HTLC Coordinator Bob ->> HTLC Plugin Counterparty: getSingleStatus() + activate HTLC Plugin Counterparty + HTLC Plugin Counterparty -->> HTLC Contract Counterparty: getSingleStatus() + activate HTLC Contract Counterparty + HTLC Contract Counterparty -->> HTLC Plugin Counterparty: ok + deactivate HTLC Contract Counterparty + HTLC Plugin Counterparty -->> HTLC Coordinator Bob: ok + deactivate HTLC Plugin Counterparty + HTLC Coordinator Bob ->> Bob: HTLC Plugin Counterparty instance + deactivate HTLC Coordinator Bob + + + Note over Bob: Bob knows his secret, so he can start the withdrawCounterparty + Bob ->> HTLC Coordinator Bob: WithdrawCounterparty() + activate HTLC Coordinator Bob + HTLC Coordinator Bob ->> HTLC Plugin Counterparty: WithdrawCounterparty() + activate HTLC Plugin Counterparty + HTLC Plugin Counterparty ->> HTLC Contract Counterparty: withdraw() + activate HTLC Contract Counterparty + HTLC Contract Counterparty -->> HTLC Plugin Counterparty: ok + deactivate HTLC Contract Counterparty + HTLC Plugin Counterparty -->> HTLC Coordinator Bob: ok + deactivate HTLC Plugin Counterparty + HTLC Coordinator Bob -->> Bob: ok + deactivate HTLC Coordinator Bob + HTLC Plugin Besu ERC20-->> HTLC Coordinator Bob: Event - CounterParty withdraw + deactivate HTLC Plugin Besu ERC20 + activate HTLC Coordinator Bob + HTLC Coordinator Bob -->> Bob: Counterparty withdraw notification + deactivate HTLC Coordinator Bob + +``` \ No newline at end of file diff --git a/extensions/cactus-plugin-htlc-coordinator/openapitools.json b/extensions/cactus-plugin-htlc-coordinator/openapitools.json new file mode 100644 index 00000000000..29f5d069907 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/openapitools.json @@ -0,0 +1,7 @@ +{ + "$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "5.2.0" + } +} diff --git a/extensions/cactus-plugin-htlc-coordinator/package.json b/extensions/cactus-plugin-htlc-coordinator/package.json new file mode 100644 index 00000000000..721aedab4d3 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/package.json @@ -0,0 +1,95 @@ +{ + "name": "@hyperledger/cactus-plugin-htlc-coordinator", + "version": "0.9.0", + "description": "HTLC Coordinator to exchange tokens between networks.", + "main": "dist/lib/main/typescript/index.js", + "mainMinified": "dist/cactus-plugin-htlc-coordinator.node.umd.min.js", + "browser": "dist/cactus-plugin-htlc-coordinator.web.umd.js", + "browserMinified": "dist/cactus-plugin-htlc-coordinator.web.umd.min.js", + "module": "dist/lib/main/typescript/index.js", + "types": "dist/types/main/typescript/index.d.ts", + "files": [ + "dist/*" + ], + "scripts": { + "generate-sdk": "openapi-generator-cli generate -i ./src/main/json/openapi.json -g typescript-axios -o ./src/main/typescript/generated/openapi/typescript-axios/", + "codegen:openapi": "run-p generate-sdk", + "codegen": "run-p 'codegen:*'", + "watch": "npm-watch", + "webpack": "npm-run-all webpack:dev webpack:prod", + "webpack:dev": "npm-run-all webpack:dev:node webpack:dev:web", + "webpack:dev:web": "webpack --env=dev --target=web --config ../../webpack.config.js", + "webpack:dev:node": "webpack --env=dev --target=node --config ../../webpack.config.js", + "webpack:prod": "npm-run-all webpack:prod:node webpack:prod:web", + "webpack:prod:web": "webpack --env=prod --target=web --config ../../webpack.config.js", + "webpack:prod:node": "webpack --env=prod --target=node --config ../../webpack.config.js" + }, + "watch": { + "codegen:openapi": { + "patterns": [ + "./src/main/json/openapi.json" + ] + } + }, + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/hyperledger/cactus.git" + }, + "keywords": [ + "Hyperledger", + "Cactus", + "Integration", + "Blockchain", + "Distributed Ledger Technology" + ], + "author": { + "name": "Hyperledger Cactus Contributors", + "email": "cactus@lists.hyperledger.org", + "url": "https://www.hyperledger.org/use/cactus" + }, + "contributors": [ + { + "name": "Please add yourself to the list of contributors", + "email": "your.name@example.com", + "url": "https://example.com" + }, + { + "name": "Peter Somogyvari", + "email": "peter.somogyvari@accenture.com", + "url": "https://accenture.com" + } + ], + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/hyperledger/cactus/issues" + }, + "homepage": "https://github.com/hyperledger/cactus#readme", + "dependencies": { + "@hyperledger/cactus-common": "0.9.0", + "@hyperledger/cactus-core": "0.9.0", + "@hyperledger/cactus-core-api": "0.9.0", + "@hyperledger/cactus-plugin-htlc-eth-besu": "0.9.0", + "@hyperledger/cactus-plugin-htlc-eth-besu-erc20": "0.9.0", + "@hyperledger/cactus-plugin-ledger-connector-besu": "0.9.0", + "@hyperledger/cactus-test-plugin-htlc-eth-besu-erc20": "0.9.0", + "axios": "0.21.1", + "body-parser": "1.19.0", + "joi": "14.3.1", + "openapi-types": "7.0.1", + "prom-client": "13.1.0", + "socket.io-client": "4.0.1", + "typescript-optional": "2.0.1" + }, + "devDependencies": { + "@hyperledger/cactus-plugin-keychain-memory": "0.9.0", + "@hyperledger/cactus-test-tooling": "0.9.0", + "@types/express": "4.17.8" + } +} diff --git a/extensions/cactus-plugin-htlc-coordinator/src/main/json/openapi.json b/extensions/cactus-plugin-htlc-coordinator/src/main/json/openapi.json new file mode 100644 index 00000000000..038c60ebac0 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/main/json/openapi.json @@ -0,0 +1,314 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Hyperledger Cactus Plugin - HTLC Coordinator", + "description": "Can exchange assets between networks", + "version": "0.0.1", + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "servers": [ + { + "url": "https://www.cactus.stream/{basePath}", + "description": "Public test instance", + "variables": { + "basePath": { + "default": "" + } + } + }, + { + "url": "http://localhost:4000/{basePath}", + "description": "Local test instance", + "variables": { + "basePath": { + "default": "" + } + } + } + ], + "components": { + "schemas": { + "OwnHTLCRequest": { + "type": "object", + "required": [ + "htlcPackage", + "connectorInstanceId", + "keychainId", + "constructorArgs", + "web3SigningCredential", + "inputAmount", + "outputAmount", + "expiration", + "hashLock", + "tokenAddress", + "receiver", + "outputNetwork", + "outputAddress" + ], + "properties": { + "htlcPackage": { + "$ref": "#/components/schemas/HtlcPackage" + }, + "connectorInstanceId": { + "description": "connector Instance Id for the connector plugin", + "type": "string", + "nullable": false + }, + "keychainId": { + "description": "keychainId for the keychain plugin", + "type": "string", + "nullable": false + }, + "constructorArgs": { + "type": "array", + "items": {}, + "default": [] + }, + "web3SigningCredential": { + "description": "Web3SigningCredential", + "$ref": "../../../../../packages/cactus-plugin-ledger-connector-besu/src/main/json/openapi.json#/components/schemas/Web3SigningCredential", + "nullable": false + }, + "inputAmount": { + "description": "Input amount to lock", + "type": "number", + "nullable": false + }, + "outputAmount": { + "description": "Output amount to lock", + "type": "number", + "nullable": false + }, + "expiration": { + "description": "Timestamp to expire the contract", + "type": "number", + "nullable": false + }, + "hashLock": { + "description": "Hashlock needed to refund the amount", + "type": "string", + "nullable": false + }, + "tokenAddress": { + "description": "The token address", + "type": "string", + "nullable": false + }, + "receiver": { + "description": "The receiver address", + "type": "string", + "nullable": false + }, + "outputNetwork": { + "description": "The output network id", + "type": "string", + "nullable": false + }, + "outputAddress": { + "description": "The output addreess to receive the tokens", + "type": "string", + "nullable": false + }, + "gas": { + "type": "number" + } + } + }, + "CounterpartyHTLCRequest": { + "type": "object", + "required": [ + "htlcPackage", + "connectorInstanceId", + "keychainId", + "htlcId", + "web3SigningCredential" + ], + "properties": { + "htlcPackage": { + "$ref": "#/components/schemas/HtlcPackage" + }, + "connectorInstanceId": { + "description": "connector Instance Id for the connector plugin", + "type": "string", + "nullable": false + }, + "keychainId": { + "description": "keychainId for the keychain plugin", + "type": "string", + "nullable": false + }, + "htlcId": { + "description": "Id for the HTLC", + "type": "string", + "nullable": false + }, + "web3SigningCredential": { + "description": "Web3SigningCredential", + "$ref": "../../../../../packages/cactus-plugin-ledger-connector-besu/src/main/json/openapi.json#/components/schemas/Web3SigningCredential", + "nullable": false + }, + "gas": { + "type": "number" + } + } + }, + "WithdrawCounterpartyRequest": { + "type": "object", + "required": [ + "htlcPackage", + "connectorInstanceId", + "keychainId", + "web3SigningCredential", + "htlcId", + "secret" + ], + "properties": { + "htlcPackage": { + "$ref": "#/components/schemas/HtlcPackage" + }, + "connectorInstanceId": { + "description": "connector Instance Id for the connector plugin", + "type": "string", + "nullable": false + }, + "keychainId": { + "description": "keychainId for the keychain plugin", + "type": "string", + "nullable": false + }, + "contractId": { + "description": "contractId for the contract", + "type": "string", + "nullable": false + }, + "web3SigningCredential": { + "description": "Web3SigningCredential", + "$ref": "../../../../../packages/cactus-plugin-ledger-connector-besu/src/main/json/openapi.json#/components/schemas/Web3SigningCredential", + "nullable": false + }, + "htlcId": { + "description": "Id for the HTLC", + "type": "string", + "nullable": false + }, + "secret": { + "description": "Counterparty HTLC secret", + "type": "string", + "nullable": false + }, + "gas": { + "type": "number" + } + } + }, + "HtlcPackage": { + "type": "string", + "enum": [ + "BESU", + "BESU_ERC20" + ] + } + } + }, + "paths": { + "/api/v1/plugins/@hyperledger/cactus-plugin-htlc-coordinator/own-htlc": { + "post": { + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cactus-plugin-htlc-coordinator/own-htlc" + } + }, + "operationId": "ownHtlcV1", + "summary": "Create an instance to interact with the own HTLC.", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OwnHTLCRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cactus-plugin-htlc-coordinator/counterparty-htlc": { + "post": { + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cactus-plugin-htlc-coordinator/counterparty-htlc" + } + }, + "operationId": "counterpartyHtlcV1", + "summary": "Create an instance to interact with the counterparty HTLC", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CounterpartyHTLCRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cactus-plugin-htlc-coordinator/withdraw-counterparty": { + "post": { + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cactus-plugin-htlc-coordinator/withdraw-counterparty" + } + }, + "operationId": "withdrawCounterpartyV1", + "summary": "Withdraw funds of the counterparty HTLC", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WithdrawCounterpartyRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore new file mode 100644 index 00000000000..57cdd7b74b9 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore @@ -0,0 +1,27 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md + +git_push.sh +.npmignore +.gitignore diff --git a/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/FILES b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/FILES new file mode 100644 index 00000000000..53250c02696 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/FILES @@ -0,0 +1,5 @@ +api.ts +base.ts +common.ts +configuration.ts +index.ts diff --git a/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION new file mode 100644 index 00000000000..7cbea073bea --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION @@ -0,0 +1 @@ +5.2.0 \ No newline at end of file diff --git a/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/api.ts b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/api.ts new file mode 100644 index 00000000000..927923def09 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/api.ts @@ -0,0 +1,551 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cactus Plugin - HTLC Coordinator + * Can exchange assets between networks + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import { Configuration } from './configuration'; +import globalAxios, { AxiosPromise, AxiosInstance } from 'axios'; +// Some imports not used depending on template conditions +// @ts-ignore +import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from './common'; +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from './base'; + +/** + * + * @export + * @interface CounterpartyHTLCRequest + */ +export interface CounterpartyHTLCRequest { + /** + * + * @type {HtlcPackage} + * @memberof CounterpartyHTLCRequest + */ + htlcPackage: HtlcPackage; + /** + * connector Instance Id for the connector plugin + * @type {string} + * @memberof CounterpartyHTLCRequest + */ + connectorInstanceId: string; + /** + * keychainId for the keychain plugin + * @type {string} + * @memberof CounterpartyHTLCRequest + */ + keychainId: string; + /** + * Id for the HTLC + * @type {string} + * @memberof CounterpartyHTLCRequest + */ + htlcId: string; + /** + * + * @type {Web3SigningCredential} + * @memberof CounterpartyHTLCRequest + */ + web3SigningCredential: Web3SigningCredential; + /** + * + * @type {number} + * @memberof CounterpartyHTLCRequest + */ + gas?: number; +} +/** + * + * @export + * @enum {string} + */ + +export enum HtlcPackage { + Besu = 'BESU', + BesuErc20 = 'BESU_ERC20' +} + +/** + * + * @export + * @interface OwnHTLCRequest + */ +export interface OwnHTLCRequest { + /** + * + * @type {HtlcPackage} + * @memberof OwnHTLCRequest + */ + htlcPackage: HtlcPackage; + /** + * connector Instance Id for the connector plugin + * @type {string} + * @memberof OwnHTLCRequest + */ + connectorInstanceId: string; + /** + * keychainId for the keychain plugin + * @type {string} + * @memberof OwnHTLCRequest + */ + keychainId: string; + /** + * + * @type {Array} + * @memberof OwnHTLCRequest + */ + constructorArgs: Array; + /** + * + * @type {Web3SigningCredential} + * @memberof OwnHTLCRequest + */ + web3SigningCredential: Web3SigningCredential; + /** + * Input amount to lock + * @type {number} + * @memberof OwnHTLCRequest + */ + inputAmount: number; + /** + * Output amount to lock + * @type {number} + * @memberof OwnHTLCRequest + */ + outputAmount: number; + /** + * Timestamp to expire the contract + * @type {number} + * @memberof OwnHTLCRequest + */ + expiration: number; + /** + * Hashlock needed to refund the amount + * @type {string} + * @memberof OwnHTLCRequest + */ + hashLock: string; + /** + * The token address + * @type {string} + * @memberof OwnHTLCRequest + */ + tokenAddress: string; + /** + * The receiver address + * @type {string} + * @memberof OwnHTLCRequest + */ + receiver: string; + /** + * The output network id + * @type {string} + * @memberof OwnHTLCRequest + */ + outputNetwork: string; + /** + * The output addreess to receive the tokens + * @type {string} + * @memberof OwnHTLCRequest + */ + outputAddress: string; + /** + * + * @type {number} + * @memberof OwnHTLCRequest + */ + gas?: number; +} +/** + * @type Web3SigningCredential + * @export + */ +export type Web3SigningCredential = Web3SigningCredentialCactusKeychainRef | Web3SigningCredentialNone | Web3SigningCredentialPrivateKeyHex; + +/** + * + * @export + * @interface Web3SigningCredentialCactusKeychainRef + */ +export interface Web3SigningCredentialCactusKeychainRef { + /** + * + * @type {Web3SigningCredentialType} + * @memberof Web3SigningCredentialCactusKeychainRef + */ + type: Web3SigningCredentialType; + /** + * The ethereum account (public key) that the credential belongs to. Basically the username in the traditional terminology of authentication. + * @type {string} + * @memberof Web3SigningCredentialCactusKeychainRef + */ + ethAccount: string; + /** + * The key to use when looking up the the keychain entry holding the secret pointed to by the keychainEntryKey parameter. + * @type {string} + * @memberof Web3SigningCredentialCactusKeychainRef + */ + keychainEntryKey: string; + /** + * The keychain ID to use when looking up the the keychain plugin instance that will be used to retrieve the secret pointed to by the keychainEntryKey parameter. + * @type {string} + * @memberof Web3SigningCredentialCactusKeychainRef + */ + keychainId: string; +} +/** + * Using this denotes that there is no signing required because the transaction is pre-signed. + * @export + * @interface Web3SigningCredentialNone + */ +export interface Web3SigningCredentialNone { + /** + * + * @type {Web3SigningCredentialType} + * @memberof Web3SigningCredentialNone + */ + type: Web3SigningCredentialType; +} +/** + * + * @export + * @interface Web3SigningCredentialPrivateKeyHex + */ +export interface Web3SigningCredentialPrivateKeyHex { + /** + * + * @type {Web3SigningCredentialType} + * @memberof Web3SigningCredentialPrivateKeyHex + */ + type: Web3SigningCredentialType; + /** + * The ethereum account (public key) that the credential belongs to. Basically the username in the traditional terminology of authentication. + * @type {string} + * @memberof Web3SigningCredentialPrivateKeyHex + */ + ethAccount: string; + /** + * The HEX encoded private key of an eth account. + * @type {string} + * @memberof Web3SigningCredentialPrivateKeyHex + */ + secret: string; +} +/** + * + * @export + * @enum {string} + */ + +export enum Web3SigningCredentialType { + CactusKeychainRef = 'CACTUS_KEYCHAIN_REF', + GethKeychainPassword = 'GETH_KEYCHAIN_PASSWORD', + PrivateKeyHex = 'PRIVATE_KEY_HEX', + None = 'NONE' +} + +/** + * + * @export + * @interface WithdrawCounterpartyRequest + */ +export interface WithdrawCounterpartyRequest { + /** + * + * @type {HtlcPackage} + * @memberof WithdrawCounterpartyRequest + */ + htlcPackage: HtlcPackage; + /** + * connector Instance Id for the connector plugin + * @type {string} + * @memberof WithdrawCounterpartyRequest + */ + connectorInstanceId: string; + /** + * keychainId for the keychain plugin + * @type {string} + * @memberof WithdrawCounterpartyRequest + */ + keychainId: string; + /** + * contractId for the contract + * @type {string} + * @memberof WithdrawCounterpartyRequest + */ + contractId?: string; + /** + * + * @type {Web3SigningCredential} + * @memberof WithdrawCounterpartyRequest + */ + web3SigningCredential: Web3SigningCredential; + /** + * Id for the HTLC + * @type {string} + * @memberof WithdrawCounterpartyRequest + */ + htlcId: string; + /** + * Counterparty HTLC secret + * @type {string} + * @memberof WithdrawCounterpartyRequest + */ + secret: string; + /** + * + * @type {number} + * @memberof WithdrawCounterpartyRequest + */ + gas?: number; +} + +/** + * DefaultApi - axios parameter creator + * @export + */ +export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * + * @summary Create an instance to interact with the counterparty HTLC + * @param {CounterpartyHTLCRequest} [counterpartyHTLCRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + counterpartyHtlcV1: async (counterpartyHTLCRequest?: CounterpartyHTLCRequest, options: any = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-htlc-coordinator/counterparty-htlc`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(counterpartyHTLCRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary Create an instance to interact with the own HTLC. + * @param {OwnHTLCRequest} [ownHTLCRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + ownHtlcV1: async (ownHTLCRequest?: OwnHTLCRequest, options: any = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-htlc-coordinator/own-htlc`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(ownHTLCRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary Withdraw funds of the counterparty HTLC + * @param {WithdrawCounterpartyRequest} [withdrawCounterpartyRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + withdrawCounterpartyV1: async (withdrawCounterpartyRequest?: WithdrawCounterpartyRequest, options: any = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-htlc-coordinator/withdraw-counterparty`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(withdrawCounterpartyRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * DefaultApi - functional programming interface + * @export + */ +export const DefaultApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration) + return { + /** + * + * @summary Create an instance to interact with the counterparty HTLC + * @param {CounterpartyHTLCRequest} [counterpartyHTLCRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async counterpartyHtlcV1(counterpartyHTLCRequest?: CounterpartyHTLCRequest, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.counterpartyHtlcV1(counterpartyHTLCRequest, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @summary Create an instance to interact with the own HTLC. + * @param {OwnHTLCRequest} [ownHTLCRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async ownHtlcV1(ownHTLCRequest?: OwnHTLCRequest, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.ownHtlcV1(ownHTLCRequest, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @summary Withdraw funds of the counterparty HTLC + * @param {WithdrawCounterpartyRequest} [withdrawCounterpartyRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async withdrawCounterpartyV1(withdrawCounterpartyRequest?: WithdrawCounterpartyRequest, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.withdrawCounterpartyV1(withdrawCounterpartyRequest, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + } +}; + +/** + * DefaultApi - factory interface + * @export + */ +export const DefaultApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = DefaultApiFp(configuration) + return { + /** + * + * @summary Create an instance to interact with the counterparty HTLC + * @param {CounterpartyHTLCRequest} [counterpartyHTLCRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + counterpartyHtlcV1(counterpartyHTLCRequest?: CounterpartyHTLCRequest, options?: any): AxiosPromise { + return localVarFp.counterpartyHtlcV1(counterpartyHTLCRequest, options).then((request) => request(axios, basePath)); + }, + /** + * + * @summary Create an instance to interact with the own HTLC. + * @param {OwnHTLCRequest} [ownHTLCRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + ownHtlcV1(ownHTLCRequest?: OwnHTLCRequest, options?: any): AxiosPromise { + return localVarFp.ownHtlcV1(ownHTLCRequest, options).then((request) => request(axios, basePath)); + }, + /** + * + * @summary Withdraw funds of the counterparty HTLC + * @param {WithdrawCounterpartyRequest} [withdrawCounterpartyRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + withdrawCounterpartyV1(withdrawCounterpartyRequest?: WithdrawCounterpartyRequest, options?: any): AxiosPromise { + return localVarFp.withdrawCounterpartyV1(withdrawCounterpartyRequest, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * DefaultApi - object-oriented interface + * @export + * @class DefaultApi + * @extends {BaseAPI} + */ +export class DefaultApi extends BaseAPI { + /** + * + * @summary Create an instance to interact with the counterparty HTLC + * @param {CounterpartyHTLCRequest} [counterpartyHTLCRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public counterpartyHtlcV1(counterpartyHTLCRequest?: CounterpartyHTLCRequest, options?: any) { + return DefaultApiFp(this.configuration).counterpartyHtlcV1(counterpartyHTLCRequest, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary Create an instance to interact with the own HTLC. + * @param {OwnHTLCRequest} [ownHTLCRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public ownHtlcV1(ownHTLCRequest?: OwnHTLCRequest, options?: any) { + return DefaultApiFp(this.configuration).ownHtlcV1(ownHTLCRequest, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary Withdraw funds of the counterparty HTLC + * @param {WithdrawCounterpartyRequest} [withdrawCounterpartyRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public withdrawCounterpartyV1(withdrawCounterpartyRequest?: WithdrawCounterpartyRequest, options?: any) { + return DefaultApiFp(this.configuration).withdrawCounterpartyV1(withdrawCounterpartyRequest, options).then((request) => request(this.axios, this.basePath)); + } +} + + diff --git a/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/base.ts b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/base.ts new file mode 100644 index 00000000000..aededdda880 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/base.ts @@ -0,0 +1,71 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cactus Plugin - HTLC Coordinator + * Can exchange assets between networks + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import { Configuration } from "./configuration"; +// Some imports not used depending on template conditions +// @ts-ignore +import globalAxios, { AxiosPromise, AxiosInstance } from 'axios'; + +export const BASE_PATH = "https://www.cactus.stream".replace(/\/+$/, ""); + +/** + * + * @export + */ +export const COLLECTION_FORMATS = { + csv: ",", + ssv: " ", + tsv: "\t", + pipes: "|", +}; + +/** + * + * @export + * @interface RequestArgs + */ +export interface RequestArgs { + url: string; + options: any; +} + +/** + * + * @export + * @class BaseAPI + */ +export class BaseAPI { + protected configuration: Configuration | undefined; + + constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) { + if (configuration) { + this.configuration = configuration; + this.basePath = configuration.basePath || this.basePath; + } + } +}; + +/** + * + * @export + * @class RequiredError + * @extends {Error} + */ +export class RequiredError extends Error { + name: "RequiredError" = "RequiredError"; + constructor(public field: string, msg?: string) { + super(msg); + } +} diff --git a/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/common.ts b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/common.ts new file mode 100644 index 00000000000..cfdff6d0707 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/common.ts @@ -0,0 +1,138 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cactus Plugin - HTLC Coordinator + * Can exchange assets between networks + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import { Configuration } from "./configuration"; +import { RequiredError, RequestArgs } from "./base"; +import { AxiosInstance } from 'axios'; + +/** + * + * @export + */ +export const DUMMY_BASE_URL = 'https://example.com' + +/** + * + * @throws {RequiredError} + * @export + */ +export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) { + if (paramValue === null || paramValue === undefined) { + throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`); + } +} + +/** + * + * @export + */ +export const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) { + if (configuration && configuration.apiKey) { + const localVarApiKeyValue = typeof configuration.apiKey === 'function' + ? await configuration.apiKey(keyParamName) + : await configuration.apiKey; + object[keyParamName] = localVarApiKeyValue; + } +} + +/** + * + * @export + */ +export const setBasicAuthToObject = function (object: any, configuration?: Configuration) { + if (configuration && (configuration.username || configuration.password)) { + object["auth"] = { username: configuration.username, password: configuration.password }; + } +} + +/** + * + * @export + */ +export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) { + if (configuration && configuration.accessToken) { + const accessToken = typeof configuration.accessToken === 'function' + ? await configuration.accessToken() + : await configuration.accessToken; + object["Authorization"] = "Bearer " + accessToken; + } +} + +/** + * + * @export + */ +export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) { + if (configuration && configuration.accessToken) { + const localVarAccessTokenValue = typeof configuration.accessToken === 'function' + ? await configuration.accessToken(name, scopes) + : await configuration.accessToken; + object["Authorization"] = "Bearer " + localVarAccessTokenValue; + } +} + +/** + * + * @export + */ +export const setSearchParams = function (url: URL, ...objects: any[]) { + const searchParams = new URLSearchParams(url.search); + for (const object of objects) { + for (const key in object) { + if (Array.isArray(object[key])) { + searchParams.delete(key); + for (const item of object[key]) { + searchParams.append(key, item); + } + } else { + searchParams.set(key, object[key]); + } + } + } + url.search = searchParams.toString(); +} + +/** + * + * @export + */ +export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) { + const nonString = typeof value !== 'string'; + const needsSerialization = nonString && configuration && configuration.isJsonMime + ? configuration.isJsonMime(requestOptions.headers['Content-Type']) + : nonString; + return needsSerialization + ? JSON.stringify(value !== undefined ? value : {}) + : (value || ""); +} + +/** + * + * @export + */ +export const toPathString = function (url: URL) { + return url.pathname + url.search + url.hash +} + +/** + * + * @export + */ +export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) { + return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { + const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || basePath) + axiosArgs.url}; + return axios.request(axiosRequestArgs); + }; +} diff --git a/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/configuration.ts b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/configuration.ts new file mode 100644 index 00000000000..73b8430748c --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/configuration.ts @@ -0,0 +1,101 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cactus Plugin - HTLC Coordinator + * Can exchange assets between networks + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface ConfigurationParameters { + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + username?: string; + password?: string; + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + basePath?: string; + baseOptions?: any; + formDataCtor?: new () => any; +} + +export class Configuration { + /** + * parameter for apiKey security + * @param name security name + * @memberof Configuration + */ + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + username?: string; + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + password?: string; + /** + * parameter for oauth2 security + * @param name security name + * @param scopes oauth2 scope + * @memberof Configuration + */ + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + /** + * override base path + * + * @type {string} + * @memberof Configuration + */ + basePath?: string; + /** + * base options for axios calls + * + * @type {any} + * @memberof Configuration + */ + baseOptions?: any; + /** + * The FormData constructor that will be used to create multipart form data + * requests. You can inject this here so that execution environments that + * do not support the FormData class can still run the generated client. + * + * @type {new () => FormData} + */ + formDataCtor?: new () => any; + + constructor(param: ConfigurationParameters = {}) { + this.apiKey = param.apiKey; + this.username = param.username; + this.password = param.password; + this.accessToken = param.accessToken; + this.basePath = param.basePath; + this.baseOptions = param.baseOptions; + this.formDataCtor = param.formDataCtor; + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + public isJsonMime(mime: string): boolean { + const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i'); + return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json'); + } +} diff --git a/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/index.ts b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/index.ts new file mode 100644 index 00000000000..df364643903 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/generated/openapi/typescript-axios/index.ts @@ -0,0 +1,18 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cactus Plugin - HTLC Coordinator + * Can exchange assets between networks + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export * from "./api"; +export * from "./configuration"; + diff --git a/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/index.ts b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/index.ts new file mode 100644 index 00000000000..87cb558397c --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/index.ts @@ -0,0 +1 @@ +export * from "./public-api"; diff --git a/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/index.web.ts b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/index.web.ts new file mode 100644 index 00000000000..cb0ff5c3b54 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/index.web.ts @@ -0,0 +1 @@ +export {}; diff --git a/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/plugin-factory-htlc-coordinator.ts b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/plugin-factory-htlc-coordinator.ts new file mode 100644 index 00000000000..4f617cc27fe --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/plugin-factory-htlc-coordinator.ts @@ -0,0 +1,21 @@ +import { + IPluginFactoryOptions, + PluginFactory, +} from "@hyperledger/cactus-core-api"; + +import { + IPluginHTLCCoordinatorOptions, + PluginHTLCCoordinator, +} from "./plugin-htlc-coordinator"; + +export class PluginFactoryHTLCCoordinator extends PluginFactory< + PluginHTLCCoordinator, + IPluginHTLCCoordinatorOptions, + IPluginFactoryOptions +> { + async create( + pluginOptions: IPluginHTLCCoordinatorOptions, + ): Promise { + return new PluginHTLCCoordinator(pluginOptions); + } +} diff --git a/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/plugin-htlc-coordinator.ts b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/plugin-htlc-coordinator.ts new file mode 100644 index 00000000000..e3cebcd9a47 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/plugin-htlc-coordinator.ts @@ -0,0 +1,362 @@ +import { Server } from "http"; +import { Server as SecureServer } from "https"; +import { v4 as uuidv4 } from "uuid"; +import { Express } from "express"; +import { promisify } from "util"; +import { Optional } from "typescript-optional"; +import OAS from "../json/openapi.json"; +import { + ICactusPlugin, + ICactusPluginOptions, + IPluginWebService, + IWebServiceEndpoint, +} from "@hyperledger/cactus-core-api"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { PluginImportType } from "@hyperledger/cactus-core-api"; +import { + Checks, + Logger, + LoggerProvider, + LogLevelDesc, +} from "@hyperledger/cactus-common"; +import { + InitializeRequest as InitializeRequestBesuERC20, + NewContractRequest as NewContractRequestBesuERC20, + PluginHtlcEthBesuErc20, + WithdrawRequest as WithdrawRequestBesuERC20, +} from "@hyperledger/cactus-plugin-htlc-eth-besu-erc20"; +import { + InitializeRequest as InitializeRequestBesu, + NewContractObj, + InvokeContractV1Response, + IPluginHtlcEthBesuOptions, + PluginFactoryHtlcEthBesu, + WithdrawReq, +} from "@hyperledger/cactus-plugin-htlc-eth-besu"; +import { PluginLedgerConnectorBesu } from "@hyperledger/cactus-plugin-ledger-connector-besu"; +import { OwnHTLCEndpoint } from "./web-services/own-htlc-endpoint"; +import { CounterpartyHTLCEndpoint } from "./web-services/counterparty-htlc-endpoint"; +import { WithdrawCounterpartyEndpoint } from "./web-services/withdraw-counterparty-endpoint"; +import { + OwnHTLCRequest, + CounterpartyHTLCRequest, + WithdrawCounterpartyRequest, + HtlcPackage, +} from "./generated/openapi/typescript-axios"; + +export interface IPluginHTLCCoordinatorOptions extends ICactusPluginOptions { + pluginRegistry: PluginRegistry; + logLevel?: LogLevelDesc; +} + +export class PluginHTLCCoordinator implements ICactusPlugin, IPluginWebService { + private readonly instanceId: string; + private readonly log: Logger; + private readonly pluginRegistry: PluginRegistry; + private endpoints: IWebServiceEndpoint[] | undefined; + private httpServer: Server | SecureServer | null = null; + + public static readonly CLASS_NAME = "PluginHTLCCoordinator"; + + public get className(): string { + return PluginHTLCCoordinator.CLASS_NAME; + } + + constructor(public readonly options: IPluginHTLCCoordinatorOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.instanceId, `${fnTag} options.instanceId`); + Checks.truthy(options.pluginRegistry, `${fnTag} options.pluginRegistry`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + + this.instanceId = options.instanceId; + this.pluginRegistry = options.pluginRegistry; + } + + public getInstanceId(): string { + return this.instanceId; + } + + public async onPluginInit(): Promise { + return; + } + + public getOpenApiSpec(): unknown { + return OAS; + } + + public getPackageName(): string { + return `@hyperledger/cactus-plugin-htlc-coordinator`; + } + + public getHttpServer(): Optional { + return Optional.ofNullable(this.httpServer); + } + + public async shutdown(): Promise { + const serverMaybe = this.getHttpServer(); + if (serverMaybe.isPresent()) { + const server = serverMaybe.get(); + await promisify(server.close.bind(server))(); + } + } + + async registerWebServices(app: Express): Promise { + const webServices = await this.getOrCreateWebServices(); + await Promise.all(webServices.map((ws) => ws.registerExpress(app))); + return webServices; + } + + public async getOrCreateWebServices(): Promise { + if (Array.isArray(this.endpoints)) { + return this.endpoints; + } + const endpoints: IWebServiceEndpoint[] = []; + { + const endpoint = new OwnHTLCEndpoint({ + coordinator: this, + logLevel: this.options.logLevel, + pluginRegistry: this.options.pluginRegistry, + }); + endpoints.push(endpoint); + } + { + const endpoint = new CounterpartyHTLCEndpoint({ + coordinator: this, + logLevel: this.options.logLevel, + pluginRegistry: this.options.pluginRegistry, + }); + endpoints.push(endpoint); + } + { + const endpoint = new WithdrawCounterpartyEndpoint({ + coordinator: this, + logLevel: this.options.logLevel, + pluginRegistry: this.options.pluginRegistry, + }); + endpoints.push(endpoint); + } + return endpoints; + } + + public async ownHTLC( + ownHTLCRequest: OwnHTLCRequest, + ): Promise { + const fnTag = `${this.className}#ownHTLC()`; + const connector = this.pluginRegistry.plugins.find( + (plugin) => plugin.getInstanceId() == ownHTLCRequest.connectorInstanceId, + ) as PluginLedgerConnectorBesu; + + const keychainId = ownHTLCRequest.keychainId; + this.log.debug("Using keychain ID:", keychainId); + + const pluginRegistry = this.options.pluginRegistry; + // pluginRegistry.add(connector); + + switch (ownHTLCRequest.htlcPackage) { + case HtlcPackage.BesuErc20: { + const pluginHtlc = (this.pluginRegistry.plugins.find((plugin) => { + return ( + plugin.getPackageName() == + "@hyperledger/cactus-plugin-htlc-eth-besu-erc20" /*&& + ((plugin as unknown) as PluginHtlcEthBesuErc20).getKeychainId() == + ownHTLCRequest.keychainId*/ + ); + }) as unknown) as PluginHtlcEthBesuErc20; + const request: InitializeRequestBesuERC20 = { + connectorId: connector.getInstanceId(), + keychainId: ownHTLCRequest.keychainId, + constructorArgs: ownHTLCRequest.constructorArgs, + web3SigningCredential: ownHTLCRequest.web3SigningCredential, + gas: ownHTLCRequest.gas, + }; + const res = await pluginHtlc.initialize(request); + if ( + res.transactionReceipt?.status == true && + res.transactionReceipt?.contractAddress != undefined + ) { + const newContractRequest: NewContractRequestBesuERC20 = { + contractAddress: res.transactionReceipt?.contractAddress, + inputAmount: ownHTLCRequest.inputAmount, + outputAmount: ownHTLCRequest.outputAmount, + expiration: ownHTLCRequest.expiration, + hashLock: ownHTLCRequest.hashLock, + tokenAddress: ownHTLCRequest.tokenAddress, + receiver: ownHTLCRequest.receiver, + outputNetwork: ownHTLCRequest.outputNetwork, + outputAddress: ownHTLCRequest.outputAddress, + connectorId: connector.getInstanceId(), + keychainId: ownHTLCRequest.keychainId, + web3SigningCredential: ownHTLCRequest.web3SigningCredential, + gas: ownHTLCRequest.gas, + }; + const res2 = await pluginHtlc.newContract(newContractRequest); + return res2; + } + } + case HtlcPackage.Besu: { + const pluginOptions: IPluginHtlcEthBesuOptions = { + instanceId: uuidv4(), + pluginRegistry, + }; + const factoryHTLC = new PluginFactoryHtlcEthBesu({ + pluginImportType: PluginImportType.Local, + }); + const pluginHtlc = await factoryHTLC.create(pluginOptions); + const request: InitializeRequestBesu = { + connectorId: connector.getInstanceId(), + keychainId: ownHTLCRequest.keychainId, + constructorArgs: ownHTLCRequest.constructorArgs, + web3SigningCredential: ownHTLCRequest.web3SigningCredential, + gas: ownHTLCRequest.gas, + }; + const res = await pluginHtlc.initialize(request); + if ( + res.transactionReceipt?.status == true && + res.transactionReceipt?.contractAddress != undefined + ) { + const newContractRequest: NewContractObj = { + contractAddress: res.transactionReceipt?.contractAddress, + outputAmount: ownHTLCRequest.outputAmount, + expiration: ownHTLCRequest.expiration, + hashLock: ownHTLCRequest.hashLock, + receiver: ownHTLCRequest.receiver, + inputAmount: ownHTLCRequest.inputAmount, + outputNetwork: ownHTLCRequest.outputNetwork, + outputAddress: ownHTLCRequest.outputAddress, + connectorId: connector.getInstanceId(), + keychainId: ownHTLCRequest.keychainId, + web3SigningCredential: ownHTLCRequest.web3SigningCredential, + gas: ownHTLCRequest.gas, + }; + const res2 = await pluginHtlc.newContract(newContractRequest); + return res2; + } + } + default: { + throw new Error( + `${fnTag} Unrecognized HTLC Package: ` + + `${ownHTLCRequest.htlcPackage} Supported ones are: ` + + `${Object.values(HtlcPackage).join(";")}`, + ); + } + } + } + + public async counterpartyHTLC( + counterpartyHTLCRequest: CounterpartyHTLCRequest, + ): Promise { + const fnTag = `${this.className}#counterpartyHTLC()`; + const pluginRegistry = this.options.pluginRegistry; + + switch (counterpartyHTLCRequest.htlcPackage) { + case HtlcPackage.BesuErc20: { + const pluginHtlc = (this.pluginRegistry.plugins.find((plugin) => { + return ( + plugin.getPackageName() == + "@hyperledger/cactus-plugin-htlc-eth-besu-erc20" /*&& + ((plugin as unknown) as PluginHtlcEthBesuErc20).getKeychainId() == + counterpartyHTLCRequest.keychainId*/ + ); + }) as unknown) as PluginHtlcEthBesuErc20; + + const res = await pluginHtlc.getSingleStatus( + counterpartyHTLCRequest.htlcId, + counterpartyHTLCRequest.connectorInstanceId, + counterpartyHTLCRequest.keychainId, + counterpartyHTLCRequest.web3SigningCredential, + ); + return res; + } + case HtlcPackage.Besu: { + const pluginOptions: IPluginHtlcEthBesuOptions = { + instanceId: uuidv4(), + pluginRegistry, + }; + const factoryHTLC = new PluginFactoryHtlcEthBesu({ + pluginImportType: PluginImportType.Local, + }); + const pluginHtlc = await factoryHTLC.create(pluginOptions); + const res = await pluginHtlc.getSingleStatus( + counterpartyHTLCRequest.htlcId, + counterpartyHTLCRequest.connectorInstanceId, + counterpartyHTLCRequest.keychainId, + counterpartyHTLCRequest.web3SigningCredential, + ); + return res; + } + default: { + throw new Error( + `${fnTag} Unrecognized HTLC Package: ` + + `${counterpartyHTLCRequest.htlcPackage} Supported ones are: ` + + `${Object.values(HtlcPackage).join(";")}`, + ); + } + } + } + public async withdrawCounterparty( + withdrawCounterpartyRequest: WithdrawCounterpartyRequest, + ): Promise { + const fnTag = `${this.className}#withdrawCounterparty()`; + // const connector = this.pluginRegistry.plugins.find( + // (plugin) => + // plugin.getInstanceId() == + // withdrawCounterpartyRequest.connectorInstanceId, + // ) as PluginLedgerConnectorBesu; + const pluginRegistry = this.options.pluginRegistry; + // pluginRegistry.add(connector); + + switch (withdrawCounterpartyRequest.htlcPackage) { + case HtlcPackage.BesuErc20: { + const pluginHtlc = (this.pluginRegistry.plugins.find((plugin) => { + return ( + plugin.getPackageName() == + "@hyperledger/cactus-plugin-htlc-eth-besu-erc20" /*&& + ((plugin as unknown) as PluginHtlcEthBesuErc20).getKeychainId() == + withdrawCounterpartyRequest.keychainId*/ + ); + }) as unknown) as PluginHtlcEthBesuErc20; + const withdrawRequest: WithdrawRequestBesuERC20 = { + id: withdrawCounterpartyRequest.htlcId, + secret: withdrawCounterpartyRequest.secret, + web3SigningCredential: + withdrawCounterpartyRequest.web3SigningCredential, + connectorId: withdrawCounterpartyRequest.connectorInstanceId, + keychainId: withdrawCounterpartyRequest.keychainId, + }; + const res = await pluginHtlc.withdraw(withdrawRequest); + return res; + } + case HtlcPackage.Besu: { + const pluginOptions: IPluginHtlcEthBesuOptions = { + instanceId: uuidv4(), + pluginRegistry, + }; + const factoryHTLC = new PluginFactoryHtlcEthBesu({ + pluginImportType: PluginImportType.Local, + }); + const pluginHtlc = await factoryHTLC.create(pluginOptions); + const withdrawRequest: WithdrawReq = { + id: withdrawCounterpartyRequest.htlcId, + secret: withdrawCounterpartyRequest.secret, + web3SigningCredential: + withdrawCounterpartyRequest.web3SigningCredential, + connectorId: withdrawCounterpartyRequest.connectorInstanceId, + keychainId: withdrawCounterpartyRequest.keychainId, + }; + const res2 = await pluginHtlc.withdraw(withdrawRequest); + return res2; + } + default: { + throw new Error( + `${fnTag} Unrecognized HTLC Package: ` + + `${withdrawCounterpartyRequest.htlcPackage} Supported ones are: ` + + `${Object.values(HtlcPackage).join(";")}`, + ); + } + } + } +} diff --git a/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/public-api.ts b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/public-api.ts new file mode 100644 index 00000000000..013367757d8 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/public-api.ts @@ -0,0 +1,17 @@ +export * from "./generated/openapi/typescript-axios/index"; +import { IPluginFactoryOptions } from "@hyperledger/cactus-core-api"; +export { + IPluginHTLCCoordinatorOptions, + PluginHTLCCoordinator, +} from "./plugin-htlc-coordinator"; + +export { PluginFactoryHTLCCoordinator } from "./plugin-factory-htlc-coordinator"; +import { PluginFactoryHTLCCoordinator } from "./plugin-factory-htlc-coordinator"; + +export * from "./generated/openapi/typescript-axios/index"; + +export async function createPluginFactory( + pluginFactoryOptions: IPluginFactoryOptions, +): Promise { + return new PluginFactoryHTLCCoordinator(pluginFactoryOptions); +} diff --git a/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/web-services/counterparty-htlc-endpoint.ts b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/web-services/counterparty-htlc-endpoint.ts new file mode 100644 index 00000000000..e7f97e6a070 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/web-services/counterparty-htlc-endpoint.ts @@ -0,0 +1,104 @@ +import { Express, Request, Response } from "express"; + +import { + IWebServiceEndpoint, + IExpressRequestHandler, + IEndpointAuthzOptions, +} from "@hyperledger/cactus-core-api"; + +import { + Logger, + Checks, + LogLevelDesc, + LoggerProvider, + IAsyncProvider, +} from "@hyperledger/cactus-common"; +import { + PluginRegistry, + registerWebServiceEndpoint, +} from "@hyperledger/cactus-core"; +import { CounterpartyHTLCRequest } from "../generated/openapi/typescript-axios"; +import { PluginHTLCCoordinator } from "../plugin-htlc-coordinator"; +import OAS from "../../json/openapi.json"; + +export interface ICounterpartyHTLCOptions { + logLevel?: LogLevelDesc; + coordinator: PluginHTLCCoordinator; + pluginRegistry: PluginRegistry; +} + +export class CounterpartyHTLCEndpoint implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "CounterpartyHTLCEndpoint"; + private readonly log: Logger; + + public get className(): string { + return CounterpartyHTLCEndpoint.CLASS_NAME; + } + + constructor(public readonly options: ICounterpartyHTLCOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.coordinator, `${fnTag} arg options.coordinator`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public getOasPath() { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cactus-plugin-htlc-coordinator/counterparty-htlc" + ]; + } + + public getPath(): string { + const apiPath = this.getOasPath(); + return apiPath.post["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + const apiPath = this.getOasPath(); + return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return this.getOasPath().post.operationId; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public async handleRequest(req: Request, res: Response): Promise { + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + this.log.debug(reqTag); + try { + const request: CounterpartyHTLCRequest = req.body as CounterpartyHTLCRequest; + const resBody = await this.options.coordinator.counterpartyHTLC(request); + res.json(resBody); + } catch (ex) { + this.log.error(`Crash while serving ${reqTag}`, ex); + res.status(500).json({ + message: "Internal Server Error", + error: ex?.stack || ex?.message, + }); + } + } +} diff --git a/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/web-services/own-htlc-endpoint.ts b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/web-services/own-htlc-endpoint.ts new file mode 100644 index 00000000000..12b73b6b52b --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/web-services/own-htlc-endpoint.ts @@ -0,0 +1,104 @@ +import { Express, Request, Response } from "express"; + +import { + IWebServiceEndpoint, + IExpressRequestHandler, + IEndpointAuthzOptions, +} from "@hyperledger/cactus-core-api"; + +import { + Logger, + Checks, + LogLevelDesc, + LoggerProvider, + IAsyncProvider, +} from "@hyperledger/cactus-common"; +import { + PluginRegistry, + registerWebServiceEndpoint, +} from "@hyperledger/cactus-core"; +import { PluginHTLCCoordinator } from "../plugin-htlc-coordinator"; +import { OwnHTLCRequest } from "../generated/openapi/typescript-axios"; +import OAS from "../../json/openapi.json"; + +export interface IOwnHTLCOptions { + logLevel?: LogLevelDesc; + coordinator: PluginHTLCCoordinator; + pluginRegistry: PluginRegistry; +} + +export class OwnHTLCEndpoint implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "OwnHTLCEndpoint"; + private readonly log: Logger; + + public get className(): string { + return OwnHTLCEndpoint.CLASS_NAME; + } + + constructor(public readonly options: IOwnHTLCOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.coordinator, `${fnTag} arg options.coordinator`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public getOasPath() { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cactus-plugin-htlc-coordinator/own-htlc" + ]; + } + + public getPath(): string { + const apiPath = this.getOasPath(); + return apiPath.post["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + const apiPath = this.getOasPath(); + return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return this.getOasPath().post.operationId; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public async handleRequest(req: Request, res: Response): Promise { + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + this.log.debug(reqTag); + try { + const request: OwnHTLCRequest = req.body as OwnHTLCRequest; + const resBody = await this.options.coordinator.ownHTLC(request); + res.json(resBody); + } catch (ex) { + this.log.error(`Crash while serving ${reqTag}`, ex); + res.status(500).json({ + message: "Internal Server Error", + error: ex?.stack || ex?.message, + }); + } + } +} diff --git a/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/web-services/withdraw-counterparty-endpoint.ts b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/web-services/withdraw-counterparty-endpoint.ts new file mode 100644 index 00000000000..f4d7e8b748f --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/main/typescript/web-services/withdraw-counterparty-endpoint.ts @@ -0,0 +1,105 @@ +import { Express, Request, Response } from "express"; + +import { + IWebServiceEndpoint, + IExpressRequestHandler, + IEndpointAuthzOptions, +} from "@hyperledger/cactus-core-api"; + +import { + Logger, + Checks, + LogLevelDesc, + LoggerProvider, + IAsyncProvider, +} from "@hyperledger/cactus-common"; +import { + PluginRegistry, + registerWebServiceEndpoint, +} from "@hyperledger/cactus-core"; +import { WithdrawCounterpartyRequest } from "../generated/openapi/typescript-axios"; +import { PluginHTLCCoordinator } from "../plugin-htlc-coordinator"; +import OAS from "../../json/openapi.json"; + +export interface IWithdrawCounterpartyOptions { + logLevel?: LogLevelDesc; + coordinator: PluginHTLCCoordinator; + pluginRegistry: PluginRegistry; +} + +export class WithdrawCounterpartyEndpoint implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "WithdrawCounterpartyEndpoint"; + private readonly log: Logger; + + public get className(): string { + return WithdrawCounterpartyEndpoint.CLASS_NAME; + } + + constructor(public readonly options: IWithdrawCounterpartyOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.coordinator, `${fnTag} arg options.coordinator`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public getOasPath() { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cactus-plugin-htlc-coordinator/withdraw-counterparty" + ]; + } + + public getPath(): string { + const apiPath = this.getOasPath(); + return apiPath.post["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + const apiPath = this.getOasPath(); + return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return this.getOasPath().post.operationId; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public async handleRequest(req: Request, res: Response): Promise { + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + this.log.debug(reqTag); + try { + const request: WithdrawCounterpartyRequest = req.body as WithdrawCounterpartyRequest; + const resBody = await this.options.coordinator.withdrawCounterparty( + request, + ); + res.json(resBody); + } catch (ex) { + res.status(500).json({ + message: "Internal Server Error", + error: ex, + }); + } + } +} diff --git a/extensions/cactus-plugin-htlc-coordinator/src/test/typescript/integration/api-surface.test.ts b/extensions/cactus-plugin-htlc-coordinator/src/test/typescript/integration/api-surface.test.ts new file mode 100644 index 00000000000..6e7cf0ea841 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/test/typescript/integration/api-surface.test.ts @@ -0,0 +1,8 @@ +import test, { Test } from "tape-promise/tape"; + +import { PluginFactoryHTLCCoordinator } from "../../../main/typescript/public-api"; + +test("Library can be loaded", (t: Test) => { + t.ok(PluginFactoryHTLCCoordinator, "PluginFactoryHTLCCoordinator truthy OK"); + t.end(); +}); diff --git a/extensions/cactus-plugin-htlc-coordinator/src/test/typescript/integration/plugin-htlc-coordinator/counterparty-htlc-endpoint.test.ts b/extensions/cactus-plugin-htlc-coordinator/src/test/typescript/integration/plugin-htlc-coordinator/counterparty-htlc-endpoint.test.ts new file mode 100644 index 00000000000..619be5984b5 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/test/typescript/integration/plugin-htlc-coordinator/counterparty-htlc-endpoint.test.ts @@ -0,0 +1,301 @@ +import test, { Test } from "tape-promise/tape"; +import { v4 as uuidv4 } from "uuid"; +import { AddressInfo } from "net"; +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import { Server as SocketIoServer } from "socket.io"; +import { + DefaultApi as HtlcCoordinatorApi, + PluginFactoryHTLCCoordinator, + IPluginHTLCCoordinatorOptions, + HtlcPackage, + OwnHTLCRequest, + CounterpartyHTLCRequest, + Configuration, +} from "../../../../main/typescript/public-api"; +import { + IPluginHtlcEthBesuErc20Options, + PluginFactoryHtlcEthBesuErc20, +} from "@hyperledger/cactus-plugin-htlc-eth-besu-erc20"; +import { + DefaultApi as BesuApi, + EthContractInvocationType, + PluginFactoryLedgerConnector, + PluginLedgerConnectorBesu, + Web3SigningCredentialType, + Web3SigningCredential, +} from "@hyperledger/cactus-plugin-ledger-connector-besu"; +import { + LogLevelDesc, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { Constants, PluginImportType } from "@hyperledger/cactus-core-api"; +import { + BesuTestLedger, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import HashTimeLockJSON from "@hyperledger/cactus-plugin-htlc-eth-besu-erc20/src/main/solidity/contracts/HashedTimeLockContract.json"; +import TestTokenJSON from "@hyperledger/cactus-test-plugin-htlc-eth-besu-erc20/src/test/solidity/token-erc20-contract/Test_Token.json"; +import DemoHelperJSON from "@hyperledger/cactus-test-plugin-htlc-eth-besu-erc20/src/test/solidity/token-erc20-contract/DemoHelpers.json"; + +const logLevel: LogLevelDesc = "INFO"; +const estimatedGas = 6721975; +const expiration = 2147483648; +const receiver = "0x627306090abaB3A6e1400e9345bC60c78a8BEf57"; +const hashLock = + "0x3c335ba7f06a8b01d0596589f73c19069e21c81e5013b91f408165d1bf623d32"; +const firstHighNetWorthAccount = "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"; +const privateKey = + "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d"; +const connectorInstanceId = uuidv4(); +const web3SigningCredential: Web3SigningCredential = { + ethAccount: firstHighNetWorthAccount, + secret: privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, +} as Web3SigningCredential; +const contractAddress = "0xCfEB869F69431e42cdB54A4F4f105C19C080A601"; + +const testCase = "Test own htlc endpoint"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning did not throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + t.comment("Starting Besu Test Ledger"); + const besuTestLedger = new BesuTestLedger(); + await besuTestLedger.start(); + + test.onFinish(async () => { + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + }); + + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + const keychainId = uuidv4(); + const keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId, + // pre-provision keychain with mock backend holding the private key of the + // test account that we'll reference while sending requests with the + // signing credential pointing to this keychain entry. + backend: new Map([ + [TestTokenJSON.contractName, JSON.stringify(TestTokenJSON)], + ]), + logLevel, + }); + keychainPlugin.set( + DemoHelperJSON.contractName, + JSON.stringify(DemoHelperJSON), + ); + keychainPlugin.set( + HashTimeLockJSON.contractName, + JSON.stringify(HashTimeLockJSON), + ); + + const factory = new PluginFactoryLedgerConnector({ + pluginImportType: PluginImportType.Local, + }); + + const pluginRegistry = new PluginRegistry({}); + const connector: PluginLedgerConnectorBesu = await factory.create({ + rpcApiHttpHost, + rpcApiWsHost, + logLevel, + instanceId: connectorInstanceId, + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + + pluginRegistry.add(connector); + + const iPluginHtlcEthBesuErc20Options: IPluginHtlcEthBesuErc20Options = { + instanceId: uuidv4(), + keychainId: keychainId, + pluginRegistry, + }; + const pluginFactoryHtlcEthBesuErc20 = new PluginFactoryHtlcEthBesuErc20({ + pluginImportType: PluginImportType.Local, + }); + const pluginHtlcEthBesuErc20 = await pluginFactoryHtlcEthBesuErc20.create( + iPluginHtlcEthBesuErc20Options, + ); + pluginRegistry.add(pluginHtlcEthBesuErc20); + + const pluginOptions: IPluginHTLCCoordinatorOptions = { + instanceId: uuidv4(), + logLevel, + pluginRegistry, + }; + const factoryHTLC = new PluginFactoryHTLCCoordinator({ + pluginImportType: PluginImportType.Local, + }); + const pluginHTLCCoordinator = await factoryHTLC.create(pluginOptions); + pluginRegistry.add(pluginHTLCCoordinator); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "0.0.0.0", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + test.onFinish(async () => await Servers.shutdown(server)); + const { address, port } = addressInfo; + const apiHost = `http://${address}:${port}`; + + const configuration = new Configuration({ basePath: apiHost }); + const htlcCoordinatorApiClient = new HtlcCoordinatorApi(configuration); + await pluginHTLCCoordinator.getOrCreateWebServices(); + await pluginHTLCCoordinator.registerWebServices(expressApp); + + const besuWsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); + const besuConnectorConfiguration = new Configuration({ basePath: apiHost }); + const besuConnectorApi = new BesuApi(besuConnectorConfiguration); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, besuWsApi as any); + + t.comment("Deploys TestToken via .json file on deployContract function"); + const deployOutToken = await connector.deployContract({ + contractName: TestTokenJSON.contractName, + contractAbi: TestTokenJSON.abi, + bytecode: TestTokenJSON.bytecode, + web3SigningCredential, + keychainId, + constructorArgs: ["100", "token", "2", "TKN"], + gas: estimatedGas, + }); + t.ok(deployOutToken, "deployContract() output is truthy OK"); + t.ok( + deployOutToken.transactionReceipt, + "deployContract() output.transactionReceipt is truthy OK", + ); + t.ok( + deployOutToken.transactionReceipt.contractAddress, + "deployContract() output.transactionReceipt.contractAddress is truthy OK", + ); + const tokenAddress = deployOutToken.transactionReceipt + .contractAddress as string; + + t.comment("Approve 10 Tokens to HashTimeLockAddress"); + const approveTokensOutput = await besuConnectorApi.invokeContractV1({ + contractName: TestTokenJSON.contractName, + keychainId, + signingCredential: web3SigningCredential, + invocationType: EthContractInvocationType.Send, + methodName: "approve", + params: [contractAddress, "10"], + gas: estimatedGas, + }); + t.equal( + approveTokensOutput.data.success, + true, + "approve() transactionReceipt.status is true OK", + ); + + t.comment("Get account balance"); + const responseBalance = await besuConnectorApi.invokeContractV1({ + contractName: TestTokenJSON.contractName, + keychainId, + signingCredential: web3SigningCredential, + invocationType: EthContractInvocationType.Call, + methodName: "balanceOf", + params: [firstHighNetWorthAccount], + }); + t.equal( + responseBalance.data.callOutput, + "100", + "balance of account is 100 OK", + ); + + t.comment("Get HashTimeLock contract and account allowance"); + const allowanceOutput = await besuConnectorApi.invokeContractV1({ + contractName: TestTokenJSON.contractName, + keychainId, + signingCredential: web3SigningCredential, + invocationType: EthContractInvocationType.Call, + methodName: "allowance", + params: [firstHighNetWorthAccount, contractAddress], + }); + t.equal(allowanceOutput.status, 200, "allowance status is 200 OK"); + t.equal(allowanceOutput.data.callOutput, "10", "allowance amount is 10 OK"); + + t.comment("Create and initialize own HTLC"); + const ownHTLCRequest: OwnHTLCRequest = { + htlcPackage: HtlcPackage.BesuErc20, + connectorInstanceId, + keychainId, + constructorArgs: [], + web3SigningCredential, + inputAmount: 10, + outputAmount: 1, + expiration, + hashLock, + tokenAddress, + receiver, + outputNetwork: "BTC", + outputAddress: "1AcVYm7M3kkJQH28FXAvyBFQzFRL6xPKu8", + gas: estimatedGas, + }; + + const response = await htlcCoordinatorApiClient.ownHtlcV1(ownHTLCRequest); + t.equal(response.status, 200, "response status is 200 OK"); + t.equal(response.data.success, true, "response success is true"); + t.ok(response.data, "pluginHTLCCoordinator.ownHtlcV1() output is truthy OK"); + t.ok( + response.data.out.transactionReceipt, + "pluginHTLCCoordinator.ownHtlcV1() output.transactionReceipt is truthy OK", + ); + + t.comment("Get HTLC id"); + const responseTxId = await besuConnectorApi.invokeContractV1({ + contractName: DemoHelperJSON.contractName, + keychainId, + signingCredential: web3SigningCredential, + invocationType: EthContractInvocationType.Call, + methodName: "getTxId", + params: [ + firstHighNetWorthAccount, + receiver, + 10, + hashLock, + expiration, + tokenAddress, + ], + gas: estimatedGas, + }); + + t.comment("Get counterparty HTLC"); + const counterpartyHTLCRequest: CounterpartyHTLCRequest = { + htlcPackage: HtlcPackage.BesuErc20, + connectorInstanceId, + keychainId, + htlcId: responseTxId.data.callOutput, + web3SigningCredential, + gas: estimatedGas, + }; + + const response2 = await htlcCoordinatorApiClient.counterpartyHtlcV1( + counterpartyHTLCRequest, + ); + t.equal(response2.status, 200, "response status is 200 OK"); + t.equal(response2.data.success, true, "response success is true"); + t.equal(response2.data.callOutput, "1", "the contract status is 1 - Active"); +}); + +test("AFTER " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning did not throw OK"); + t.end(); +}); diff --git a/extensions/cactus-plugin-htlc-coordinator/src/test/typescript/integration/plugin-htlc-coordinator/own-htlc-endpoint.test.ts b/extensions/cactus-plugin-htlc-coordinator/src/test/typescript/integration/plugin-htlc-coordinator/own-htlc-endpoint.test.ts new file mode 100644 index 00000000000..b4dcb6710bb --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/test/typescript/integration/plugin-htlc-coordinator/own-htlc-endpoint.test.ts @@ -0,0 +1,266 @@ +import test, { Test } from "tape-promise/tape"; +import { v4 as uuidv4 } from "uuid"; +import { AddressInfo } from "net"; +import express from "express"; +import bodyParser from "body-parser"; +import { Server as SocketIoServer } from "socket.io"; +import http from "http"; +import { + DefaultApi as HtlcCoordinatorApi, + PluginFactoryHTLCCoordinator, + IPluginHTLCCoordinatorOptions, + HtlcPackage, + OwnHTLCRequest, + Configuration, +} from "../../../../main/typescript/public-api"; +import { + DefaultApi as BesuApi, + EthContractInvocationType, + PluginFactoryLedgerConnector, + PluginLedgerConnectorBesu, + Web3SigningCredentialType, + Web3SigningCredential, +} from "@hyperledger/cactus-plugin-ledger-connector-besu"; +import { + IPluginHtlcEthBesuErc20Options, + PluginFactoryHtlcEthBesuErc20, +} from "@hyperledger/cactus-plugin-htlc-eth-besu-erc20"; +import { + LogLevelDesc, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { Constants, PluginImportType } from "@hyperledger/cactus-core-api"; +import { + BesuTestLedger, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import HashTimeLockJSON from "@hyperledger/cactus-plugin-htlc-eth-besu-erc20/src/main/solidity/contracts/HashedTimeLockContract.json"; +import TestTokenJSON from "@hyperledger/cactus-test-plugin-htlc-eth-besu-erc20/src/test/solidity/token-erc20-contract/Test_Token.json"; +import DemoHelperJSON from "@hyperledger/cactus-test-plugin-htlc-eth-besu-erc20/src/test/solidity/token-erc20-contract/DemoHelpers.json"; + +const logLevel: LogLevelDesc = "INFO"; +const estimatedGas = 6721975; +const expiration = 2147483648; +const receiver = "0x627306090abaB3A6e1400e9345bC60c78a8BEf57"; +const hashLock = + "0x3c335ba7f06a8b01d0596589f73c19069e21c81e5013b91f408165d1bf623d32"; +const firstHighNetWorthAccount = "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"; +const privateKey = + "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d"; +const connectorInstanceId = uuidv4(); +const web3SigningCredential: Web3SigningCredential = { + ethAccount: firstHighNetWorthAccount, + secret: privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, +} as Web3SigningCredential; +const contractAddress = "0xCfEB869F69431e42cdB54A4F4f105C19C080A601"; + +const testCase = "Test own htlc endpoint"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning did not throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + t.comment("Starting Besu Test Ledger"); + const besuTestLedger = new BesuTestLedger(); + await besuTestLedger.start(); + + test.onFinish(async () => { + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + }); + + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + const keychainId = uuidv4(); + const keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId, + // pre-provision keychain with mock backend holding the private key of the + // test account that we'll reference while sending requests with the + // signing credential pointing to this keychain entry. + backend: new Map([ + [TestTokenJSON.contractName, JSON.stringify(TestTokenJSON)], + ]), + logLevel, + }); + keychainPlugin.set( + DemoHelperJSON.contractName, + JSON.stringify(DemoHelperJSON), + ); + keychainPlugin.set( + HashTimeLockJSON.contractName, + JSON.stringify(HashTimeLockJSON), + ); + + const factory = new PluginFactoryLedgerConnector({ + pluginImportType: PluginImportType.Local, + }); + + const pluginRegistry = new PluginRegistry({}); + const connector: PluginLedgerConnectorBesu = await factory.create({ + rpcApiHttpHost, + rpcApiWsHost, + logLevel, + instanceId: connectorInstanceId, + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + + const iPluginHtlcEthBesuErc20Options: IPluginHtlcEthBesuErc20Options = { + instanceId: uuidv4(), + keychainId: keychainId, + pluginRegistry, + }; + const pluginFactoryHtlcEthBesuErc20 = new PluginFactoryHtlcEthBesuErc20({ + pluginImportType: PluginImportType.Local, + }); + const pluginHtlcEthBesuErc20 = await pluginFactoryHtlcEthBesuErc20.create( + iPluginHtlcEthBesuErc20Options, + ); + pluginRegistry.add(pluginHtlcEthBesuErc20); + + pluginRegistry.add(connector); + const pluginOptions: IPluginHTLCCoordinatorOptions = { + instanceId: uuidv4(), + logLevel, + pluginRegistry, + }; + + const factoryHTLC = new PluginFactoryHTLCCoordinator({ + pluginImportType: PluginImportType.Local, + }); + const pluginHTLCCoordinator = await factoryHTLC.create(pluginOptions); + pluginRegistry.add(pluginHTLCCoordinator); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "0.0.0.0", + port: 0, + server: server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + test.onFinish(async () => await Servers.shutdown(server)); + const { address, port } = addressInfo; + const apiHost = `http://${address}:${port}`; + + const configuration = new Configuration({ basePath: apiHost }); + const htlcCoordinatorApi = new HtlcCoordinatorApi(configuration); + await pluginHTLCCoordinator.getOrCreateWebServices(); + await pluginHTLCCoordinator.registerWebServices(expressApp); + + const besuWsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); + const besuConnectorConfiguration = new Configuration({ basePath: apiHost }); + const besuConnectorApi = new BesuApi(besuConnectorConfiguration); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, besuWsApi as any); + + t.comment("Deploys TestToken via .json file on deployContract function"); + const deployOutToken = await connector.deployContract({ + contractName: TestTokenJSON.contractName, + contractAbi: TestTokenJSON.abi, + bytecode: TestTokenJSON.bytecode, + web3SigningCredential, + keychainId, + constructorArgs: ["100", "token", "2", "TKN"], + gas: estimatedGas, + }); + t.ok(deployOutToken, "deployContract() output is truthy OK"); + t.ok( + deployOutToken.transactionReceipt, + "deployContract() output.transactionReceipt is truthy OK", + ); + t.ok( + deployOutToken.transactionReceipt.contractAddress, + "deployContract() output.transactionReceipt.contractAddress is truthy OK", + ); + const tokenAddress = deployOutToken.transactionReceipt + .contractAddress as string; + + t.comment("Approve 10 Tokens to HashTimeLockAddress"); + const approveTokensOutput = await besuConnectorApi.invokeContractV1({ + contractName: TestTokenJSON.contractName, + keychainId, + signingCredential: web3SigningCredential, + invocationType: EthContractInvocationType.Send, + methodName: "approve", + params: [contractAddress, "10"], + gas: estimatedGas, + }); + t.equal( + approveTokensOutput.data.success, + true, + "approve() transactionReceipt.status is true OK", + ); + + t.comment("Get account balance"); + const responseBalance = await besuConnectorApi.invokeContractV1({ + contractName: TestTokenJSON.contractName, + keychainId, + signingCredential: web3SigningCredential, + invocationType: EthContractInvocationType.Call, + methodName: "balanceOf", + params: [firstHighNetWorthAccount], + }); + t.equal( + responseBalance.data.callOutput, + "100", + "balance of account is 100 OK", + ); + + t.comment("Get HashTimeLock contract and account allowance"); + const allowanceOutput = await besuConnectorApi.invokeContractV1({ + contractName: TestTokenJSON.contractName, + keychainId, + signingCredential: web3SigningCredential, + invocationType: EthContractInvocationType.Call, + methodName: "allowance", + params: [firstHighNetWorthAccount, contractAddress], + }); + t.equal(allowanceOutput.status, 200, "allowance status is 200 OK"); + t.equal(allowanceOutput.data.callOutput, "10", "allowance amount is 10 OK"); + + t.comment("Create and initialize own HTLC"); + const ownHTLCRequest: OwnHTLCRequest = { + htlcPackage: HtlcPackage.BesuErc20, + connectorInstanceId, + keychainId, + constructorArgs: [], + web3SigningCredential, + inputAmount: 10, + outputAmount: 1, + expiration, + hashLock, + tokenAddress, + receiver, + outputNetwork: "BTC", + outputAddress: "1AcVYm7M3kkJQH28FXAvyBFQzFRL6xPKu8", + gas: estimatedGas, + }; + + const response = await htlcCoordinatorApi.ownHtlcV1(ownHTLCRequest); + t.equal(response.status, 200, "response status is 200 OK"); + t.equal(response.data.success, true, "response success is true"); + t.ok(response.data, "pluginHTLCCoordinator.ownHtlcV1() output is truthy OK"); + t.ok( + response.data.out.transactionReceipt, + "pluginHTLCCoordinator.ownHtlcV1() output.transactionReceipt is truthy OK", + ); + t.end(); +}); + +test("AFTER " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning did not throw OK"); + t.end(); +}); diff --git a/extensions/cactus-plugin-htlc-coordinator/src/test/typescript/integration/plugin-htlc-coordinator/refund.test.ts b/extensions/cactus-plugin-htlc-coordinator/src/test/typescript/integration/plugin-htlc-coordinator/refund.test.ts new file mode 100644 index 00000000000..7573f7f3312 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/test/typescript/integration/plugin-htlc-coordinator/refund.test.ts @@ -0,0 +1,356 @@ +import test, { Test } from "tape-promise/tape"; +import { v4 as uuidv4 } from "uuid"; +import { AddressInfo } from "net"; +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import { Server as SocketIoServer } from "socket.io"; +import { + DefaultApi as HtlcCoordinatorApi, + PluginFactoryHTLCCoordinator, + IPluginHTLCCoordinatorOptions, + HtlcPackage, + OwnHTLCRequest, + CounterpartyHTLCRequest, + WithdrawCounterpartyRequest, + Configuration, +} from "../../../../main/typescript/public-api"; +import { + DefaultApi as BesuApi, + EthContractInvocationType, + PluginFactoryLedgerConnector, + PluginLedgerConnectorBesu, + Web3SigningCredentialType, + Web3SigningCredential, +} from "@hyperledger/cactus-plugin-ledger-connector-besu"; +import { + IPluginHtlcEthBesuErc20Options, + PluginFactoryHtlcEthBesuErc20, +} from "@hyperledger/cactus-plugin-htlc-eth-besu-erc20"; +import { + LogLevelDesc, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { Constants, PluginImportType } from "@hyperledger/cactus-core-api"; +import { + BesuTestLedger, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; + +import HashTimeLockJSON from "@hyperledger/cactus-plugin-htlc-eth-besu-erc20/src/main/solidity/contracts/HashedTimeLockContract.json"; +import TestTokenJSON from "@hyperledger/cactus-test-plugin-htlc-eth-besu-erc20/src/test/solidity/token-erc20-contract/Test_Token.json"; +import DemoHelperJSON from "@hyperledger/cactus-test-plugin-htlc-eth-besu-erc20/src/test/solidity/token-erc20-contract/DemoHelpers.json"; + +const logLevel: LogLevelDesc = "INFO"; +const estimatedGas = 6721975; +const expiration = 2147483648; +const incorrect_secret = + "0x4853485acd2bfc3c632026ee365279743af107a30492e3ceaa7aefc30c2a048a"; +const receiver = "0x627306090abaB3A6e1400e9345bC60c78a8BEf57"; +const hashLock = + "0x3c335ba7f06a8b01d0596589f73c19069e21c81e5013b91f408165d1bf623d32"; +const firstHighNetWorthAccount = "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"; +const privateKey = + "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d"; +const connectorInstanceId = uuidv4(); +const web3SigningCredential: Web3SigningCredential = { + ethAccount: firstHighNetWorthAccount, + secret: privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, +} as Web3SigningCredential; +const contractAddress = "0xCfEB869F69431e42cdB54A4F4f105C19C080A601"; + +const testCase = "Test own htlc endpoint"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning did not throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + t.comment("Starting Besu Test Ledger"); + const besuTestLedger = new BesuTestLedger(); + await besuTestLedger.start(); + + test.onFinish(async () => { + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + }); + + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + const keychainId = uuidv4(); + const keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId, + // pre-provision keychain with mock backend holding the private key of the + // test account that we'll reference while sending requests with the + // signing credential pointing to this keychain entry. + backend: new Map([ + [TestTokenJSON.contractName, JSON.stringify(TestTokenJSON)], + ]), + logLevel, + }); + keychainPlugin.set( + DemoHelperJSON.contractName, + JSON.stringify(DemoHelperJSON), + ); + keychainPlugin.set( + HashTimeLockJSON.contractName, + JSON.stringify(HashTimeLockJSON), + ); + + const factory = new PluginFactoryLedgerConnector({ + pluginImportType: PluginImportType.Local, + }); + + const pluginRegistry = new PluginRegistry({}); + const connector: PluginLedgerConnectorBesu = await factory.create({ + rpcApiHttpHost, + rpcApiWsHost, + logLevel, + instanceId: connectorInstanceId, + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + pluginRegistry.add(connector); + + const iPluginHtlcEthBesuErc20Options: IPluginHtlcEthBesuErc20Options = { + instanceId: uuidv4(), + keychainId: keychainId, + pluginRegistry, + }; + const pluginFactoryHtlcEthBesuErc20 = new PluginFactoryHtlcEthBesuErc20({ + pluginImportType: PluginImportType.Local, + }); + const pluginHtlcEthBesuErc20 = await pluginFactoryHtlcEthBesuErc20.create( + iPluginHtlcEthBesuErc20Options, + ); + pluginRegistry.add(pluginHtlcEthBesuErc20); + + const pluginOptions: IPluginHTLCCoordinatorOptions = { + instanceId: uuidv4(), + logLevel, + pluginRegistry, + }; + const factoryHTLC = new PluginFactoryHTLCCoordinator({ + pluginImportType: PluginImportType.Local, + }); + const pluginHTLCCoordinator = await factoryHTLC.create(pluginOptions); + pluginRegistry.add(pluginHTLCCoordinator); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "0.0.0.0", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + test.onFinish(async () => await Servers.shutdown(server)); + const { address, port } = addressInfo; + const apiHost = `http://${address}:${port}`; + + const configuration = new Configuration({ basePath: apiHost }); + const htlcCoordinatorApi = new HtlcCoordinatorApi(configuration); + + await pluginHTLCCoordinator.getOrCreateWebServices(); + await pluginHTLCCoordinator.registerWebServices(expressApp); + const besuWsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); + const besuConnectorConfiguration = new Configuration({ basePath: apiHost }); + const besuConnectorApi = new BesuApi(besuConnectorConfiguration); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, besuWsApi as any); + + t.comment("Deploys TestToken via .json file on deployContract function"); + const deployOutToken = await connector.deployContract({ + contractName: TestTokenJSON.contractName, + contractAbi: TestTokenJSON.abi, + bytecode: TestTokenJSON.bytecode, + web3SigningCredential, + keychainId, + constructorArgs: ["100", "token", "2", "TKN"], + gas: estimatedGas, + }); + t.ok(deployOutToken, "deployContract() output is truthy OK"); + t.ok( + deployOutToken.transactionReceipt, + "deployContract() output.transactionReceipt is truthy OK", + ); + t.ok( + deployOutToken.transactionReceipt.contractAddress, + "deployContract() output.transactionReceipt.contractAddress is truthy OK", + ); + const tokenAddress = deployOutToken.transactionReceipt + .contractAddress as string; + + t.comment("Approve 10 Tokens to HashTimeLockAddress"); + const approveTokensOutput = await besuConnectorApi.invokeContractV1({ + contractName: TestTokenJSON.contractName, + keychainId, + signingCredential: web3SigningCredential, + invocationType: EthContractInvocationType.Send, + methodName: "approve", + params: [contractAddress, "10"], + gas: estimatedGas, + }); + t.equal( + approveTokensOutput.data.success, + true, + "approve() transactionReceipt.status is true OK", + ); + + t.comment("Get account balance"); + const responseBalance = await besuConnectorApi.invokeContractV1({ + contractName: TestTokenJSON.contractName, + keychainId, + signingCredential: web3SigningCredential, + invocationType: EthContractInvocationType.Call, + methodName: "balanceOf", + params: [firstHighNetWorthAccount], + }); + t.equal( + responseBalance.data.callOutput, + "100", + "balance of account is 100 OK", + ); + + t.comment("Get HashTimeLock contract and account allowance"); + const allowanceOutput = await besuConnectorApi.invokeContractV1({ + contractName: TestTokenJSON.contractName, + keychainId, + signingCredential: web3SigningCredential, + invocationType: EthContractInvocationType.Call, + methodName: "allowance", + params: [firstHighNetWorthAccount, contractAddress], + }); + t.equal(allowanceOutput.status, 200, "allowance status is 200 OK"); + t.equal(allowanceOutput.data.callOutput, "10", "allowance amount is 10 OK"); + + t.comment("Create and initialize own HTLC"); + const ownHTLCRequest: OwnHTLCRequest = { + htlcPackage: HtlcPackage.BesuErc20, + connectorInstanceId, + keychainId, + constructorArgs: [], + web3SigningCredential, + inputAmount: 10, + outputAmount: 1, + expiration, + hashLock, + tokenAddress, + receiver, + outputNetwork: "BTC", + outputAddress: "1AcVYm7M3kkJQH28FXAvyBFQzFRL6xPKu8", + gas: estimatedGas, + }; + + const response = await htlcCoordinatorApi.ownHtlcV1(ownHTLCRequest); + t.equal(response.status, 200, "response status is 200 OK"); + t.equal(response.data.success, true, "response success is true"); + t.ok(response.data, "pluginHTLCCoordinator.ownHtlcV1() output is truthy OK"); + t.ok( + response.data.out.transactionReceipt, + "pluginHTLCCoordinator.ownHtlcV1() output.transactionReceipt is truthy OK", + ); + + t.comment("Get HTLC id"); + const responseTxId = await besuConnectorApi.invokeContractV1({ + contractName: DemoHelperJSON.contractName, + keychainId, + signingCredential: web3SigningCredential, + invocationType: EthContractInvocationType.Call, + methodName: "getTxId", + params: [ + firstHighNetWorthAccount, + receiver, + 10, + hashLock, + expiration, + tokenAddress, + ], + gas: estimatedGas, + }); + + t.comment("Get counterparty HTLC"); + const counterpartyHTLCRequest: CounterpartyHTLCRequest = { + htlcPackage: HtlcPackage.BesuErc20, + connectorInstanceId, + keychainId, + htlcId: responseTxId.data.callOutput, + web3SigningCredential, + gas: estimatedGas, + }; + + const response2 = await htlcCoordinatorApi.counterpartyHtlcV1( + counterpartyHTLCRequest, + ); + t.equal(response2.status, 200, "response status is 200 OK"); + t.equal(response2.data.success, true, "response success is true"); + t.equal(response2.data.callOutput, "1", "the contract status is 1 - Active"); + + t.comment("Withdraw with incorrect secret"); + const withdrawCounterparty: WithdrawCounterpartyRequest = { + htlcPackage: HtlcPackage.BesuErc20, + connectorInstanceId, + keychainId, + web3SigningCredential, + htlcId: responseTxId.data.callOutput, + secret: incorrect_secret, + gas: estimatedGas, + }; + + try { + await htlcCoordinatorApi.withdrawCounterpartyV1(withdrawCounterparty); + } catch (exp) { + const revertReason = exp.response.data.error.receipt.revertReason; + const regExp = new RegExp(/0e494e56414c49445f5345435245540/); + const rejectMsg = "response === throws OK"; + t.match(revertReason, regExp, rejectMsg); + } + + t.comment("Get balance of receiver account"); + const responseFinalBalanceReceiver = await connector.invokeContract({ + contractName: TestTokenJSON.contractName, + keychainId, + signingCredential: web3SigningCredential, + invocationType: EthContractInvocationType.Call, + methodName: "balanceOf", + params: [receiver], + }); + t.equal( + responseFinalBalanceReceiver.callOutput, + "0", + "balance of receiver account is 0 OK", + ); + + t.comment("Get balance of sender account"); + const responseFinalBalanceSender = await connector.invokeContract({ + contractName: TestTokenJSON.contractName, + keychainId, + signingCredential: web3SigningCredential, + invocationType: EthContractInvocationType.Call, + methodName: "balanceOf", + params: [firstHighNetWorthAccount], + }); + t.equal( + responseFinalBalanceSender.callOutput, + "90", + "balance of sender account is 90 OK", + ); + + t.end(); +}); + +test("AFTER " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning did not throw OK"); + t.end(); +}); diff --git a/extensions/cactus-plugin-htlc-coordinator/src/test/typescript/integration/plugin-htlc-coordinator/withdraw-counterparty-endpoint.test.ts b/extensions/cactus-plugin-htlc-coordinator/src/test/typescript/integration/plugin-htlc-coordinator/withdraw-counterparty-endpoint.test.ts new file mode 100644 index 00000000000..89d5ac945e5 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/test/typescript/integration/plugin-htlc-coordinator/withdraw-counterparty-endpoint.test.ts @@ -0,0 +1,352 @@ +import test, { Test } from "tape-promise/tape"; +import { v4 as uuidv4 } from "uuid"; +import { AddressInfo } from "net"; +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import { Server as SocketIoServer } from "socket.io"; +import { + DefaultApi as HtlcCoordinatorApi, + PluginFactoryHTLCCoordinator, + IPluginHTLCCoordinatorOptions, + HtlcPackage, + OwnHTLCRequest, + CounterpartyHTLCRequest, + WithdrawCounterpartyRequest, + Configuration, +} from "../../../../main/typescript/public-api"; +import { + DefaultApi as BesuApi, + EthContractInvocationType, + PluginFactoryLedgerConnector, + PluginLedgerConnectorBesu, + Web3SigningCredentialType, + Web3SigningCredential, +} from "@hyperledger/cactus-plugin-ledger-connector-besu"; +import { + IPluginHtlcEthBesuErc20Options, + PluginFactoryHtlcEthBesuErc20, +} from "@hyperledger/cactus-plugin-htlc-eth-besu-erc20"; +import { + LogLevelDesc, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { Constants, PluginImportType } from "@hyperledger/cactus-core-api"; +import { + BesuTestLedger, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; + +import HashTimeLockJSON from "@hyperledger/cactus-plugin-htlc-eth-besu-erc20/src/main/solidity/contracts/HashedTimeLockContract.json"; +import TestTokenJSON from "@hyperledger/cactus-test-plugin-htlc-eth-besu-erc20/src/test/solidity/token-erc20-contract/Test_Token.json"; +import DemoHelperJSON from "@hyperledger/cactus-test-plugin-htlc-eth-besu-erc20/src/test/solidity/token-erc20-contract/DemoHelpers.json"; + +const logLevel: LogLevelDesc = "INFO"; +const estimatedGas = 6721975; +const expiration = 2147483648; +const secret = + "0x3853485acd2bfc3c632026ee365279743af107a30492e3ceaa7aefc30c2a048a"; +const receiver = "0x627306090abaB3A6e1400e9345bC60c78a8BEf57"; +const hashLock = + "0x3c335ba7f06a8b01d0596589f73c19069e21c81e5013b91f408165d1bf623d32"; +const firstHighNetWorthAccount = "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"; +const privateKey = + "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d"; +const connectorInstanceId = uuidv4(); +const web3SigningCredential: Web3SigningCredential = { + ethAccount: firstHighNetWorthAccount, + secret: privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, +} as Web3SigningCredential; +const contractAddress = "0xCfEB869F69431e42cdB54A4F4f105C19C080A601"; + +const testCase = "Test own htlc endpoint"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning did not throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + t.comment("Starting Besu Test Ledger"); + const besuTestLedger = new BesuTestLedger(); + await besuTestLedger.start(); + + test.onFinish(async () => { + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + }); + + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + const keychainId = uuidv4(); + const keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId, + // pre-provision keychain with mock backend holding the private key of the + // test account that we'll reference while sending requests with the + // signing credential pointing to this keychain entry. + backend: new Map([ + [TestTokenJSON.contractName, JSON.stringify(TestTokenJSON)], + ]), + logLevel, + }); + keychainPlugin.set( + DemoHelperJSON.contractName, + JSON.stringify(DemoHelperJSON), + ); + keychainPlugin.set( + HashTimeLockJSON.contractName, + JSON.stringify(HashTimeLockJSON), + ); + + const factory = new PluginFactoryLedgerConnector({ + pluginImportType: PluginImportType.Local, + }); + + const pluginRegistry = new PluginRegistry({}); + const connector: PluginLedgerConnectorBesu = await factory.create({ + rpcApiHttpHost, + rpcApiWsHost, + logLevel, + instanceId: connectorInstanceId, + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + pluginRegistry.add(connector); + + const iPluginHtlcEthBesuErc20Options: IPluginHtlcEthBesuErc20Options = { + instanceId: uuidv4(), + keychainId: keychainId, + pluginRegistry, + }; + const pluginFactoryHtlcEthBesuErc20 = new PluginFactoryHtlcEthBesuErc20({ + pluginImportType: PluginImportType.Local, + }); + const pluginHtlcEthBesuErc20 = await pluginFactoryHtlcEthBesuErc20.create( + iPluginHtlcEthBesuErc20Options, + ); + pluginRegistry.add(pluginHtlcEthBesuErc20); + + const pluginOptions: IPluginHTLCCoordinatorOptions = { + instanceId: uuidv4(), + logLevel, + pluginRegistry, + }; + const factoryHTLC = new PluginFactoryHTLCCoordinator({ + pluginImportType: PluginImportType.Local, + }); + const pluginHTLCCoordinator = await factoryHTLC.create(pluginOptions); + pluginRegistry.add(pluginHTLCCoordinator); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "0.0.0.0", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + test.onFinish(async () => await Servers.shutdown(server)); + const { address, port } = addressInfo; + const apiHost = `http://${address}:${port}`; + + const configuration = new Configuration({ basePath: apiHost }); + const htlcCoordinatorApi = new HtlcCoordinatorApi(configuration); + + await pluginHTLCCoordinator.getOrCreateWebServices(); + await pluginHTLCCoordinator.registerWebServices(expressApp); + const besuWsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); + const besuConnectorConfiguration = new Configuration({ basePath: apiHost }); + const besuConnectorApi = new BesuApi(besuConnectorConfiguration); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, besuWsApi as any); + + t.comment("Deploys TestToken via .json file on deployContract function"); + const deployOutToken = await connector.deployContract({ + contractName: TestTokenJSON.contractName, + contractAbi: TestTokenJSON.abi, + bytecode: TestTokenJSON.bytecode, + web3SigningCredential, + keychainId, + constructorArgs: ["100", "token", "2", "TKN"], + gas: estimatedGas, + }); + t.ok(deployOutToken, "deployContract() output is truthy OK"); + t.ok( + deployOutToken.transactionReceipt, + "deployContract() output.transactionReceipt is truthy OK", + ); + t.ok( + deployOutToken.transactionReceipt.contractAddress, + "deployContract() output.transactionReceipt.contractAddress is truthy OK", + ); + const tokenAddress = deployOutToken.transactionReceipt + .contractAddress as string; + + t.comment("Approve 10 Tokens to HashTimeLockAddress"); + const approveTokensOutput = await besuConnectorApi.invokeContractV1({ + contractName: TestTokenJSON.contractName, + keychainId, + signingCredential: web3SigningCredential, + invocationType: EthContractInvocationType.Send, + methodName: "approve", + params: [contractAddress, "10"], + gas: estimatedGas, + }); + t.equal( + approveTokensOutput.data.success, + true, + "approve() transactionReceipt.status is true OK", + ); + + t.comment("Get account balance"); + const responseBalance = await besuConnectorApi.invokeContractV1({ + contractName: TestTokenJSON.contractName, + keychainId, + signingCredential: web3SigningCredential, + invocationType: EthContractInvocationType.Call, + methodName: "balanceOf", + params: [firstHighNetWorthAccount], + }); + t.equal( + responseBalance.data.callOutput, + "100", + "balance of account is 100 OK", + ); + + t.comment("Get HashTimeLock contract and account allowance"); + const allowanceOutput = await besuConnectorApi.invokeContractV1({ + contractName: TestTokenJSON.contractName, + keychainId, + signingCredential: web3SigningCredential, + invocationType: EthContractInvocationType.Call, + methodName: "allowance", + params: [firstHighNetWorthAccount, contractAddress], + }); + t.equal(allowanceOutput.status, 200, "allowance status is 200 OK"); + t.equal(allowanceOutput.data.callOutput, "10", "allowance amount is 10 OK"); + + t.comment("Create and initialize own HTLC"); + const ownHTLCRequest: OwnHTLCRequest = { + htlcPackage: HtlcPackage.BesuErc20, + connectorInstanceId, + keychainId, + constructorArgs: [], + web3SigningCredential, + inputAmount: 10, + outputAmount: 1, + expiration, + hashLock, + tokenAddress, + receiver, + outputNetwork: "BTC", + outputAddress: "1AcVYm7M3kkJQH28FXAvyBFQzFRL6xPKu8", + gas: estimatedGas, + }; + + const response = await htlcCoordinatorApi.ownHtlcV1(ownHTLCRequest); + t.equal(response.status, 200, "response status is 200 OK"); + t.equal(response.data.success, true, "response success is true"); + t.ok(response.data, "pluginHTLCCoordinator.ownHtlcV1() output is truthy OK"); + t.ok( + response.data.out.transactionReceipt, + "pluginHTLCCoordinator.ownHtlcV1() output.transactionReceipt is truthy OK", + ); + + t.comment("Get HTLC id"); + const responseTxId = await besuConnectorApi.invokeContractV1({ + contractName: DemoHelperJSON.contractName, + keychainId, + signingCredential: web3SigningCredential, + invocationType: EthContractInvocationType.Call, + methodName: "getTxId", + params: [ + firstHighNetWorthAccount, + receiver, + 10, + hashLock, + expiration, + tokenAddress, + ], + gas: estimatedGas, + }); + + t.comment("Get counterparty HTLC"); + const counterpartyHTLCRequest: CounterpartyHTLCRequest = { + htlcPackage: HtlcPackage.BesuErc20, + connectorInstanceId, + keychainId, + htlcId: responseTxId.data.callOutput, + web3SigningCredential, + gas: estimatedGas, + }; + + const response2 = await htlcCoordinatorApi.counterpartyHtlcV1( + counterpartyHTLCRequest, + ); + t.equal(response2.status, 200, "response status is 200 OK"); + t.equal(response2.data.success, true, "response success is true"); + t.equal(response2.data.callOutput, "1", "the contract status is 1 - Active"); + + t.comment("Get counterparty HTLC"); + const withdrawCounterparty: WithdrawCounterpartyRequest = { + htlcPackage: HtlcPackage.BesuErc20, + connectorInstanceId, + keychainId, + web3SigningCredential, + htlcId: responseTxId.data.callOutput, + secret, + gas: estimatedGas, + }; + + const response3 = await htlcCoordinatorApi.withdrawCounterpartyV1( + withdrawCounterparty, + ); + t.equal(response3.status, 200, "response status is 200 OK"); + t.equal(response3.data.success, true, "response success is true"); + + t.comment("Get balance of sender account"); + const responseFinalBalanceSender = await connector.invokeContract({ + contractName: TestTokenJSON.contractName, + keychainId, + signingCredential: web3SigningCredential, + invocationType: EthContractInvocationType.Call, + methodName: "balanceOf", + params: [firstHighNetWorthAccount], + }); + t.equal( + responseFinalBalanceSender.callOutput, + "90", + "balance of sender account is 90 OK", + ); + + t.comment("Get balance of receiver account"); + const responseFinalBalanceReceiver = await connector.invokeContract({ + contractName: TestTokenJSON.contractName, + keychainId, + signingCredential: web3SigningCredential, + invocationType: EthContractInvocationType.Call, + methodName: "balanceOf", + params: [receiver], + }); + t.equal( + responseFinalBalanceReceiver.callOutput, + "10", + "balance of receiver account is 10 OK", + ); + t.end(); +}); + +test("AFTER " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning did not throw OK"); + t.end(); +}); diff --git a/extensions/cactus-plugin-htlc-coordinator/src/test/typescript/unit/api-surface.test.ts b/extensions/cactus-plugin-htlc-coordinator/src/test/typescript/unit/api-surface.test.ts new file mode 100644 index 00000000000..6e7cf0ea841 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/src/test/typescript/unit/api-surface.test.ts @@ -0,0 +1,8 @@ +import test, { Test } from "tape-promise/tape"; + +import { PluginFactoryHTLCCoordinator } from "../../../main/typescript/public-api"; + +test("Library can be loaded", (t: Test) => { + t.ok(PluginFactoryHTLCCoordinator, "PluginFactoryHTLCCoordinator truthy OK"); + t.end(); +}); diff --git a/extensions/cactus-plugin-htlc-coordinator/tsconfig.json b/extensions/cactus-plugin-htlc-coordinator/tsconfig.json new file mode 100644 index 00000000000..70dc020c673 --- /dev/null +++ b/extensions/cactus-plugin-htlc-coordinator/tsconfig.json @@ -0,0 +1,45 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "allowJs": true, + "outDir": "./dist/lib/", + "declarationDir": "dist/types", + "resolveJsonModule": true, + "rootDir": "./src", + "tsBuildInfoFile": "../../.build-cache/cactus-plugin-htlc-coordinator.tsbuildinfo" + }, + "include": [ + "./src", + "src/**/*.json", + ], + "references": [ + { + "path": "../../packages/cactus-common/tsconfig.json" + }, + { + "path": "../../packages/cactus-core/tsconfig.json" + }, + { + "path": "../../packages/cactus-core-api/tsconfig.json" + }, + { + "path": "../../packages/cactus-test-tooling/tsconfig.json" + }, + { + "path": "../../packages/cactus-core-api/tsconfig.json" + }, + { + "path": "../../packages/cactus-plugin-ledger-connector-besu/tsconfig.json" + }, + { + "path": "../../packages/cactus-plugin-htlc-eth-besu-erc20/tsconfig.json" + }, + { + "path": "../../packages/cactus-plugin-htlc-eth-besu/tsconfig.json" + }, + { + "path": "../../packages/cactus-test-plugin-htlc-eth-besu-erc20/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/packages/cactus-plugin-htlc-eth-besu-erc20/src/main/typescript/plugin-htlc-eth-besu-erc20.ts b/packages/cactus-plugin-htlc-eth-besu-erc20/src/main/typescript/plugin-htlc-eth-besu-erc20.ts index ca59385663a..a9cdf00fb0c 100644 --- a/packages/cactus-plugin-htlc-eth-besu-erc20/src/main/typescript/plugin-htlc-eth-besu-erc20.ts +++ b/packages/cactus-plugin-htlc-eth-besu-erc20/src/main/typescript/plugin-htlc-eth-besu-erc20.ts @@ -36,6 +36,7 @@ import { } from "./generated/openapi/typescript-axios"; export interface IPluginHtlcEthBesuErc20Options extends ICactusPluginOptions { + keychainId?: string; instanceId: string; logLevel?: LogLevelDesc; pluginRegistry: PluginRegistry; @@ -44,6 +45,7 @@ export class PluginHtlcEthBesuErc20 implements ICactusPlugin, IPluginWebService { public static readonly CLASS_NAME = "PluginHtlcEthBesuErc20"; private readonly instanceId: string; + private readonly keychainId: string; private readonly pluginRegistry: PluginRegistry; private readonly estimatedGas = 6721975; private endpoints: IWebServiceEndpoint[] | undefined; @@ -55,6 +57,7 @@ export class PluginHtlcEthBesuErc20 Checks.truthy(opts.pluginRegistry, `${fnTag} opts.pluginRegistry`); Checks.nonBlankString(opts.instanceId, `${fnTag} opts.instanceId`); this.instanceId = opts.instanceId; + this.keychainId = opts.keychainId || " "; this.pluginRegistry = opts.pluginRegistry; } @@ -88,6 +91,10 @@ export class PluginHtlcEthBesuErc20 return this.instanceId; } + public getKeychainId(): string { + return this.keychainId; + } + public getPackageName(): string { return "@hyperledger/cactus-plugin-htlc-eth-besu-erc20"; } diff --git a/packages/cactus-plugin-ledger-connector-besu/README.md b/packages/cactus-plugin-ledger-connector-besu/README.md index 1a4714494d9..cb048a77398 100644 --- a/packages/cactus-plugin-ledger-connector-besu/README.md +++ b/packages/cactus-plugin-ledger-connector-besu/README.md @@ -25,7 +25,7 @@ your local machine for development and testing purposes. In the root of the project to install the dependencies execute the command: ```sh -npm run comfigure +npm run configure ``` ### Compiling