From ca1f4afb366c7901128e9a0b71fc808429b407c2 Mon Sep 17 00:00:00 2001
From: peterli-r3 <51169685+peterli-r3@users.noreply.github.com>
Date: Mon, 8 Jul 2024 14:38:14 +0800
Subject: [PATCH] Add SendTransaction Cordapp (#31)
* added sendAndRecieve cordapp
* added Java sendAndRecieve
* fixed up readmes
* clean up sendAndRecieveTransaction
* updated readme
* update readme
---------
Co-authored-by: Faris Alblooki <66366646+parisyup@users.noreply.github.com>
---
.../sendAndRecieveTransaction/.ci/Jenkinsfile | 10 +
.../.ci/nightly/JenkinsfileSnykScan | 6 +
.../sendAndRecieveTransaction/.gitignore | 86 +++++
.../runConfigurations/DebugCorDapp.run.xml | 15 +
java-samples/sendAndRecieveTransaction/.snyk | 14 +
.../FlowManagementUI/Dockerfile | 5 +
.../FlowManagementUI/README.md | 84 +++++
.../FlowManagementUI/app.py | 12 +
.../FlowManagementUI/requirements.txt | 1 +
.../FlowManagementUI/static/Scripts/script.js | 322 ++++++++++++++++++
.../FlowManagementUI/static/css/main.css | 202 +++++++++++
.../FlowManagementUI/templates/index.html | 87 +++++
.../sendAndRecieveTransaction/README.md | 120 +++++++
.../sendAndRecieveTransaction/aliceVault.png | Bin 0 -> 17869 bytes
.../sendAndRecieveTransaction/build.gradle | 73 ++++
.../charlieAfter.png | Bin 0 -> 50718 bytes
.../charlieBefore.png | Bin 0 -> 9976 bytes
.../config/combined-worker-compose.yaml | 87 +++++
.../config/gradle-plugin-default-key.pem | 13 +
.../config/log4j2.xml | 51 +++
.../config/r3-ca-key.pem | 32 ++
.../config/static-network-config.json | 23 ++
.../contracts/build.gradle | 86 +++++
.../obligation/contracts/IOUContract.java | 50 +++
.../samples/obligation/states/IOUState.java | 81 +++++
.../gradle.properties | 73 ++++
.../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 61574 bytes
.../gradle/wrapper/gradle-wrapper.properties | 6 +
.../sendAndRecieveTransaction/gradlew | 244 +++++++++++++
.../sendAndRecieveTransaction/gradlew.bat | 92 +++++
.../sendAndRecieveTransaction/settings.gradle | 25 ++
.../workflows/build.gradle | 96 ++++++
.../obligation/workflows/FinalizeIOUFlow.java | 125 +++++++
.../obligation/workflows/IOUIssueFlow.java | 139 ++++++++
.../workflows/IOUIssueFlowArgs.java | 23 ++
.../obligation/workflows/IOUSettleFlow.java | 127 +++++++
.../workflows/IOUSettleFlowArgs.java | 25 ++
.../obligation/workflows/IOUTransferFlow.java | 127 +++++++
.../workflows/IOUTransferFlowArgs.java | 26 ++
.../obligation/workflows/ListIOUFlow.java | 59 ++++
.../workflows/ListIOUFlowResults.java | 44 +++
.../workflows/ReceiveTransactionFlow.java | 26 ++
.../workflows/sendAndReceiveTransaction.java | 99 ++++++
.../sendAndRecieveTransactionArgs.java | 33 ++
.../samples/obligation/MyFirstFlowTest.java | 41 +++
.../sendAndRecieveTransaction/.ci/Jenkinsfile | 11 +
.../.ci/nightly/JenkinsfileSnykScan | 6 +
.../sendAndRecieveTransaction/.gitignore | 86 +++++
.../runConfigurations/DebugCorDapp.run.xml | 15 +
.../sendAndRecieveTransaction/.snyk | 14 +
.../FlowManagementUI/Dockerfile | 5 +
.../FlowManagementUI/README.md | 84 +++++
.../FlowManagementUI/app.py | 12 +
.../FlowManagementUI/requirements.txt | 1 +
.../FlowManagementUI/static/Scripts/script.js | 322 ++++++++++++++++++
.../FlowManagementUI/static/css/main.css | 202 +++++++++++
.../FlowManagementUI/templates/index.html | 87 +++++
.../sendAndRecieveTransaction/README.md | 120 +++++++
.../sendAndRecieveTransaction/aliceVault.png | Bin 0 -> 17869 bytes
.../sendAndRecieveTransaction/build.gradle | 88 +++++
.../charlieAfter.png | Bin 0 -> 50718 bytes
.../charlieBefore.png | Bin 0 -> 9976 bytes
.../config/combined-worker-compose.yaml | 87 +++++
.../config/gradle-plugin-default-key.pem | 13 +
.../config/log4j2.xml | 51 +++
.../config/r3-ca-key.pem | 32 ++
.../config/static-network-config.json | 23 ++
.../contracts/build.gradle | 88 +++++
.../obligation/contracts/IOUContract.kt | 55 +++
.../samples/obligation/states/IOUState.kt | 36 ++
.../gradle.properties | 73 ++++
.../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 61574 bytes
.../gradle/wrapper/gradle-wrapper.properties | 6 +
.../sendAndRecieveTransaction/gradlew | 244 +++++++++++++
.../sendAndRecieveTransaction/gradlew.bat | 92 +++++
.../sendAndRecieveTransaction/settings.gradle | 25 ++
.../workflows/build.gradle | 98 ++++++
.../obligation/workflows/FinalizeIOUFlow.kt | 98 ++++++
.../obligation/workflows/IOUIssueFlow.kt | 119 +++++++
.../obligation/workflows/IOUSettleFlow.kt | 119 +++++++
.../obligation/workflows/IOUTransferFlow.kt | 116 +++++++
.../obligation/workflows/ListIOUFlow.kt | 58 ++++
.../sendAndRecieveTransactionFlow.kt | 110 ++++++
.../samples/obligation/MyFirstFlowTest.kt | 45 +++
84 files changed, 5531 insertions(+)
create mode 100644 java-samples/sendAndRecieveTransaction/.ci/Jenkinsfile
create mode 100644 java-samples/sendAndRecieveTransaction/.ci/nightly/JenkinsfileSnykScan
create mode 100644 java-samples/sendAndRecieveTransaction/.gitignore
create mode 100644 java-samples/sendAndRecieveTransaction/.run/runConfigurations/DebugCorDapp.run.xml
create mode 100644 java-samples/sendAndRecieveTransaction/.snyk
create mode 100644 java-samples/sendAndRecieveTransaction/FlowManagementUI/Dockerfile
create mode 100644 java-samples/sendAndRecieveTransaction/FlowManagementUI/README.md
create mode 100644 java-samples/sendAndRecieveTransaction/FlowManagementUI/app.py
create mode 100644 java-samples/sendAndRecieveTransaction/FlowManagementUI/requirements.txt
create mode 100644 java-samples/sendAndRecieveTransaction/FlowManagementUI/static/Scripts/script.js
create mode 100644 java-samples/sendAndRecieveTransaction/FlowManagementUI/static/css/main.css
create mode 100644 java-samples/sendAndRecieveTransaction/FlowManagementUI/templates/index.html
create mode 100644 java-samples/sendAndRecieveTransaction/README.md
create mode 100644 java-samples/sendAndRecieveTransaction/aliceVault.png
create mode 100644 java-samples/sendAndRecieveTransaction/build.gradle
create mode 100644 java-samples/sendAndRecieveTransaction/charlieAfter.png
create mode 100644 java-samples/sendAndRecieveTransaction/charlieBefore.png
create mode 100644 java-samples/sendAndRecieveTransaction/config/combined-worker-compose.yaml
create mode 100644 java-samples/sendAndRecieveTransaction/config/gradle-plugin-default-key.pem
create mode 100644 java-samples/sendAndRecieveTransaction/config/log4j2.xml
create mode 100644 java-samples/sendAndRecieveTransaction/config/r3-ca-key.pem
create mode 100644 java-samples/sendAndRecieveTransaction/config/static-network-config.json
create mode 100644 java-samples/sendAndRecieveTransaction/contracts/build.gradle
create mode 100644 java-samples/sendAndRecieveTransaction/contracts/src/main/java/com/r3/developers/samples/obligation/contracts/IOUContract.java
create mode 100644 java-samples/sendAndRecieveTransaction/contracts/src/main/java/com/r3/developers/samples/obligation/states/IOUState.java
create mode 100644 java-samples/sendAndRecieveTransaction/gradle.properties
create mode 100644 java-samples/sendAndRecieveTransaction/gradle/wrapper/gradle-wrapper.jar
create mode 100644 java-samples/sendAndRecieveTransaction/gradle/wrapper/gradle-wrapper.properties
create mode 100644 java-samples/sendAndRecieveTransaction/gradlew
create mode 100644 java-samples/sendAndRecieveTransaction/gradlew.bat
create mode 100644 java-samples/sendAndRecieveTransaction/settings.gradle
create mode 100644 java-samples/sendAndRecieveTransaction/workflows/build.gradle
create mode 100644 java-samples/sendAndRecieveTransaction/workflows/src/main/java/com/r3/developers/samples/obligation/workflows/FinalizeIOUFlow.java
create mode 100644 java-samples/sendAndRecieveTransaction/workflows/src/main/java/com/r3/developers/samples/obligation/workflows/IOUIssueFlow.java
create mode 100644 java-samples/sendAndRecieveTransaction/workflows/src/main/java/com/r3/developers/samples/obligation/workflows/IOUIssueFlowArgs.java
create mode 100644 java-samples/sendAndRecieveTransaction/workflows/src/main/java/com/r3/developers/samples/obligation/workflows/IOUSettleFlow.java
create mode 100644 java-samples/sendAndRecieveTransaction/workflows/src/main/java/com/r3/developers/samples/obligation/workflows/IOUSettleFlowArgs.java
create mode 100644 java-samples/sendAndRecieveTransaction/workflows/src/main/java/com/r3/developers/samples/obligation/workflows/IOUTransferFlow.java
create mode 100644 java-samples/sendAndRecieveTransaction/workflows/src/main/java/com/r3/developers/samples/obligation/workflows/IOUTransferFlowArgs.java
create mode 100644 java-samples/sendAndRecieveTransaction/workflows/src/main/java/com/r3/developers/samples/obligation/workflows/ListIOUFlow.java
create mode 100644 java-samples/sendAndRecieveTransaction/workflows/src/main/java/com/r3/developers/samples/obligation/workflows/ListIOUFlowResults.java
create mode 100644 java-samples/sendAndRecieveTransaction/workflows/src/main/java/com/r3/developers/samples/obligation/workflows/ReceiveTransactionFlow.java
create mode 100644 java-samples/sendAndRecieveTransaction/workflows/src/main/java/com/r3/developers/samples/obligation/workflows/sendAndReceiveTransaction.java
create mode 100644 java-samples/sendAndRecieveTransaction/workflows/src/main/java/com/r3/developers/samples/obligation/workflows/sendAndRecieveTransactionArgs.java
create mode 100644 java-samples/sendAndRecieveTransaction/workflows/src/test/java/com/r3/developers/samples/obligation/MyFirstFlowTest.java
create mode 100644 kotlin-samples/sendAndRecieveTransaction/.ci/Jenkinsfile
create mode 100644 kotlin-samples/sendAndRecieveTransaction/.ci/nightly/JenkinsfileSnykScan
create mode 100644 kotlin-samples/sendAndRecieveTransaction/.gitignore
create mode 100644 kotlin-samples/sendAndRecieveTransaction/.run/runConfigurations/DebugCorDapp.run.xml
create mode 100644 kotlin-samples/sendAndRecieveTransaction/.snyk
create mode 100644 kotlin-samples/sendAndRecieveTransaction/FlowManagementUI/Dockerfile
create mode 100644 kotlin-samples/sendAndRecieveTransaction/FlowManagementUI/README.md
create mode 100644 kotlin-samples/sendAndRecieveTransaction/FlowManagementUI/app.py
create mode 100644 kotlin-samples/sendAndRecieveTransaction/FlowManagementUI/requirements.txt
create mode 100644 kotlin-samples/sendAndRecieveTransaction/FlowManagementUI/static/Scripts/script.js
create mode 100644 kotlin-samples/sendAndRecieveTransaction/FlowManagementUI/static/css/main.css
create mode 100644 kotlin-samples/sendAndRecieveTransaction/FlowManagementUI/templates/index.html
create mode 100644 kotlin-samples/sendAndRecieveTransaction/README.md
create mode 100644 kotlin-samples/sendAndRecieveTransaction/aliceVault.png
create mode 100644 kotlin-samples/sendAndRecieveTransaction/build.gradle
create mode 100644 kotlin-samples/sendAndRecieveTransaction/charlieAfter.png
create mode 100644 kotlin-samples/sendAndRecieveTransaction/charlieBefore.png
create mode 100644 kotlin-samples/sendAndRecieveTransaction/config/combined-worker-compose.yaml
create mode 100644 kotlin-samples/sendAndRecieveTransaction/config/gradle-plugin-default-key.pem
create mode 100644 kotlin-samples/sendAndRecieveTransaction/config/log4j2.xml
create mode 100644 kotlin-samples/sendAndRecieveTransaction/config/r3-ca-key.pem
create mode 100644 kotlin-samples/sendAndRecieveTransaction/config/static-network-config.json
create mode 100644 kotlin-samples/sendAndRecieveTransaction/contracts/build.gradle
create mode 100644 kotlin-samples/sendAndRecieveTransaction/contracts/src/main/kotlin/com/r3/developers/samples/obligation/contracts/IOUContract.kt
create mode 100644 kotlin-samples/sendAndRecieveTransaction/contracts/src/main/kotlin/com/r3/developers/samples/obligation/states/IOUState.kt
create mode 100644 kotlin-samples/sendAndRecieveTransaction/gradle.properties
create mode 100644 kotlin-samples/sendAndRecieveTransaction/gradle/wrapper/gradle-wrapper.jar
create mode 100644 kotlin-samples/sendAndRecieveTransaction/gradle/wrapper/gradle-wrapper.properties
create mode 100644 kotlin-samples/sendAndRecieveTransaction/gradlew
create mode 100644 kotlin-samples/sendAndRecieveTransaction/gradlew.bat
create mode 100644 kotlin-samples/sendAndRecieveTransaction/settings.gradle
create mode 100644 kotlin-samples/sendAndRecieveTransaction/workflows/build.gradle
create mode 100644 kotlin-samples/sendAndRecieveTransaction/workflows/src/main/kotlin/com/r3/developers/samples/obligation/workflows/FinalizeIOUFlow.kt
create mode 100644 kotlin-samples/sendAndRecieveTransaction/workflows/src/main/kotlin/com/r3/developers/samples/obligation/workflows/IOUIssueFlow.kt
create mode 100644 kotlin-samples/sendAndRecieveTransaction/workflows/src/main/kotlin/com/r3/developers/samples/obligation/workflows/IOUSettleFlow.kt
create mode 100644 kotlin-samples/sendAndRecieveTransaction/workflows/src/main/kotlin/com/r3/developers/samples/obligation/workflows/IOUTransferFlow.kt
create mode 100644 kotlin-samples/sendAndRecieveTransaction/workflows/src/main/kotlin/com/r3/developers/samples/obligation/workflows/ListIOUFlow.kt
create mode 100644 kotlin-samples/sendAndRecieveTransaction/workflows/src/main/kotlin/com/r3/developers/samples/obligation/workflows/sendAndRecieveTransactionFlow.kt
create mode 100644 kotlin-samples/sendAndRecieveTransaction/workflows/src/test/kotlin/com/r3/developers/samples/obligation/MyFirstFlowTest.kt
diff --git a/java-samples/sendAndRecieveTransaction/.ci/Jenkinsfile b/java-samples/sendAndRecieveTransaction/.ci/Jenkinsfile
new file mode 100644
index 0000000..f926354
--- /dev/null
+++ b/java-samples/sendAndRecieveTransaction/.ci/Jenkinsfile
@@ -0,0 +1,10 @@
+@Library('corda-shared-build-pipeline-steps@5.0') _
+
+cordaPipeline(
+ nexusAppId: 'com.corda.CSDE-Java.5.0',
+ publishRepoPrefix: '',
+ slimBuild: true,
+ runUnitTests: false,
+ dedicatedJobForSnykDelta: false,
+ slackChannel: '#corda-corda5-dev-ex-build-notifications'
+ )
diff --git a/java-samples/sendAndRecieveTransaction/.ci/nightly/JenkinsfileSnykScan b/java-samples/sendAndRecieveTransaction/.ci/nightly/JenkinsfileSnykScan
new file mode 100644
index 0000000..fc2b1ee
--- /dev/null
+++ b/java-samples/sendAndRecieveTransaction/.ci/nightly/JenkinsfileSnykScan
@@ -0,0 +1,6 @@
+@Library('corda-shared-build-pipeline-steps@5.0') _
+
+cordaSnykScanPipeline (
+ snykTokenId: 'r3-snyk-corda5',
+ snykAdditionalCommands: "--all-sub-projects -d"
+)
diff --git a/java-samples/sendAndRecieveTransaction/.gitignore b/java-samples/sendAndRecieveTransaction/.gitignore
new file mode 100644
index 0000000..d2879c4
--- /dev/null
+++ b/java-samples/sendAndRecieveTransaction/.gitignore
@@ -0,0 +1,86 @@
+
+# Eclipse, ctags, Mac metadata, log files
+.classpath
+.project
+.settings
+tags
+.DS_Store
+*.log
+*.orig
+
+# Created by .ignore support plugin (hsz.mobi)
+
+.gradle
+local.properties
+.gradletasknamecache
+
+# General build files
+**/build/*
+
+lib/quasar.jar
+
+**/logs/*
+
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
+
+*.iml
+
+## Directory-based project format:
+.idea/*.xml
+.idea/.name
+.idea/copyright
+.idea/inspectionProfiles
+.idea/libraries
+.idea/shelf
+.idea/dataSources
+.idea/markdown-navigator
+.idea/runConfigurations
+.idea/dictionaries
+
+
+# Include the -parameters compiler option by default in IntelliJ required for serialization.
+!.idea/codeStyleSettings.xml
+
+
+## File-based project format:
+*.ipr
+*.iws
+
+## Plugin-specific files:
+
+# IntelliJ
+**/out/
+/classes/
+
+
+
+# vim
+*.swp
+*.swn
+*.swo
+
+
+
+# Directory generated during Resolve and TestOSGi gradle tasks
+bnd/
+
+# Ignore Gradle build output directory
+build
+/.idea/codeStyles/codeStyleConfig.xml
+/.idea/codeStyles/Project.xml
+
+
+
+# Ignore Visual studio directory
+bin/
+
+
+
+*.cpi
+*.cpb
+*.cpk
+workspace/**
+
+# ingore temporary data files
+*.dat
\ No newline at end of file
diff --git a/java-samples/sendAndRecieveTransaction/.run/runConfigurations/DebugCorDapp.run.xml b/java-samples/sendAndRecieveTransaction/.run/runConfigurations/DebugCorDapp.run.xml
new file mode 100644
index 0000000..1d8da82
--- /dev/null
+++ b/java-samples/sendAndRecieveTransaction/.run/runConfigurations/DebugCorDapp.run.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java-samples/sendAndRecieveTransaction/.snyk b/java-samples/sendAndRecieveTransaction/.snyk
new file mode 100644
index 0000000..c04521f
--- /dev/null
+++ b/java-samples/sendAndRecieveTransaction/.snyk
@@ -0,0 +1,14 @@
+# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
+version: v1.25.0
+# ignores vulnerabilities until expiry date; change duration by modifying expiry date
+ignore:
+ SNYK-JAVA-ORGJETBRAINSKOTLIN-2393744:
+ - '*':
+ reason: >-
+ This vulnerability relates to information exposure via creation of
+ temporary files (via Kotlin functions) with insecure permissions.
+ Corda does not use any of the vulnerable functions so it is not
+ susceptible to this vulnerability
+ expires: 2023-10-19T17:15:26.836Z
+ created: 2023-02-02T17:15:26.839Z
+patch: {}
diff --git a/java-samples/sendAndRecieveTransaction/FlowManagementUI/Dockerfile b/java-samples/sendAndRecieveTransaction/FlowManagementUI/Dockerfile
new file mode 100644
index 0000000..016bc21
--- /dev/null
+++ b/java-samples/sendAndRecieveTransaction/FlowManagementUI/Dockerfile
@@ -0,0 +1,5 @@
+FROM python
+WORKDIR /app
+COPY . /app
+RUN pip install -r requirements.txt
+CMD ["python3", "app.py"]
\ No newline at end of file
diff --git a/java-samples/sendAndRecieveTransaction/FlowManagementUI/README.md b/java-samples/sendAndRecieveTransaction/FlowManagementUI/README.md
new file mode 100644
index 0000000..fffff29
--- /dev/null
+++ b/java-samples/sendAndRecieveTransaction/FlowManagementUI/README.md
@@ -0,0 +1,84 @@
+# Corda 5 CorDapp Flow Management Tool
+
+
+This user guide provides step-by-step instructions on using the Corda 5 flow management tool. This article will help you learn how to connect the running corDapp, make flow calls, configure flow queries, and retrieve results.
+
+## Prerequisites
+* Install and run Python and Flask framework. link.
+
+* Prepare your local Corda 5 environment. (By default, the Flow Management Tool is looking to connect to https://localhost:8888/api/v5_2/swagger#/ with Login: Admin and Password: Admin.)
+
+* Clong the Flow Management Tool repository. FlowManagementUI: main
+
+## Set Up
+
+1. Assuming your local Corda 5 environment is populated and the swagger endpoint is at: https://localhost:8888/api/v5_2/swagger#/
+
+2. Navigate to where you downloaded the Corda 5 Flow Management Tool
+
+3. To run the framework
+ * Navigate to the file name using cd command.
+ * use the python app.py command to run it.
+ ![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/f0c3bf59-8180-48a0-91cc-80f2d260e530)
+
+ * Later on, click on the IP Address which will open the Interface:
+
+![image(4)](https://github.com/parisyup/FlowManagementUI/assets/66366646/8d88e37c-edbb-4d6d-8bcd-d773e818a106)
+
+
+The Flow Management Tool should be automatically connected with the CorDapp running locally from your CSDE. You can test the connection by click on the dropdown list at the Flow Initiator section. You should be able to see the vNodes of your started CorDapp from CSDE.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/5a2356f2-cd14-489c-abd0-4afe0bf0d251)
+
+## Set Up With Docker
+
+1- Open up Command Prompt
+
+2- Navigate to the application folder using the CD commands
+
+3- Ensure that Docker application is open and build the image using the following command:
+`docker build -t your-image-name .`
+
+Make sure to include the dot at the end of the command
+
+the `your-image-name` at the end of the command can be whatever you like but make sure to use the same name in the next step
+
+4- Run the docker image using the following command:
+`docker run --rm -it --expose 8888 -p 5000:5000 your-image-name`
+
+5- You can access the website by using https://localhost:5000 or https://127.0.0.1:5000
+
+## Using the Flow Management Tool
+
+### Selecting the Flow Initiator
+
+As the first step of using the Flow Management Tool, you would need to select the Flow Initiator. The Flow Initiator indicates which vNode will be triggering the flow. If you wish to have Alice to run a transaction to Bob, select the X500Name of Alice. The selected vNode’s shortHash (Corda 5 Network participant identifier) will also be shown below the dropdown list to signify your selection.
+
+### Function 1: To Make a Flow Call
+
+1. Click on "Flow Call" tab in the application.
+2. Paste the your JSON format request body into the request input box.
+3. Click Post button to trigger the call.
+
+![image(5)](https://github.com/parisyup/FlowManagementUI/assets/66366646/c65195a6-0a70-4354-804e-37884f657746)
+
+
+### Function 2: To Configure Flow Query
+
+1. Click on the “Flow Query” tab.
+2. Choose whether to query a single flow or all flows at the selected Flow Initiator.
+3. If you choose to query all of the flows, select “Query All Flows“ then click “Get“.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/0482cfa4-7ee1-42f2-8786-2d8ad80b2936)
+4. If you choose to query a single flow, select “Query Single Flow“, please add the ClientID in specified filed.
+5. Click on “Get” to retrieve the result.
+
+![image(6)](https://github.com/parisyup/FlowManagementUI/assets/66366646/13e979b0-f76e-4f2c-9d55-81be8880890b)
+
+If you have any suggestions or questions, feel free to give us your feedback through Github for a better experience in the future!
+
+## Conclusion
+In summary, our project introduces a specialized flow management layer on top of Swagger for Corda developers. We understand the challenges developers face in testing Corda applications due to the complexity of commands, our solution focuses on simplifying the process.
+
+Our all-in-one flow management system provides developers with a unified platform, streamlining development and enhancing efficiency. A key feature allows developers to run flows directly from an externally developed website and monitor their status in real-time, offering a user-friendly and practical solution for Corda developers. Overall, our project aims to make Corda development more accessible and tailored to the specific needs of flow management.
+
diff --git a/java-samples/sendAndRecieveTransaction/FlowManagementUI/app.py b/java-samples/sendAndRecieveTransaction/FlowManagementUI/app.py
new file mode 100644
index 0000000..5d3e3f6
--- /dev/null
+++ b/java-samples/sendAndRecieveTransaction/FlowManagementUI/app.py
@@ -0,0 +1,12 @@
+from flask import Flask
+from flask import render_template
+app = Flask(__name__)
+
+
+@app.route('/')
+def home(): # put application's code here
+ return render_template("index.html")
+
+if __name__ == '__main__':
+ app.run(debug=True, host='0.0.0.0')
+
diff --git a/java-samples/sendAndRecieveTransaction/FlowManagementUI/requirements.txt b/java-samples/sendAndRecieveTransaction/FlowManagementUI/requirements.txt
new file mode 100644
index 0000000..8ab6294
--- /dev/null
+++ b/java-samples/sendAndRecieveTransaction/FlowManagementUI/requirements.txt
@@ -0,0 +1 @@
+flask
\ No newline at end of file
diff --git a/java-samples/sendAndRecieveTransaction/FlowManagementUI/static/Scripts/script.js b/java-samples/sendAndRecieveTransaction/FlowManagementUI/static/Scripts/script.js
new file mode 100644
index 0000000..8fd4ec0
--- /dev/null
+++ b/java-samples/sendAndRecieveTransaction/FlowManagementUI/static/Scripts/script.js
@@ -0,0 +1,322 @@
+// This script contains functions for making API requests, handling data, and updating the UI.
+
+// Variable to store the selected X500Name from the dropdown
+let selectedX500Name;
+
+// Variable to indicate whether data is currently being loaded from the pull request
+let loading = false;
+
+// Function to make a GET request to an external API and return the data
+function getData() {
+ // Replace the URL with the actual API endpoint
+ return fetch('https://jsonplaceholder.typicode.com/todos/1')
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+
+ // Return specific data fields
+ return {
+ id: data.id,
+ title: data.title
+ };
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get the data and display it on the page
+function getDataAndDisplay() {
+ getData()
+ .then(result => {
+ // Update the result div with the data
+ document.getElementById('result').innerHTML = `
+
ID: ${result.id}
+
Title: ${result.title}
+ `;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a GET request to retrieve CPI data
+function getCPI() {
+ const url = 'https://localhost:8888/api/v1/cpi';
+ // Perform the GET request with authorization headers
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+ // Further processing of the data can be done here
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get all virtual nodes and populate a dropdown with the data
+function getAllVirtualNodes() {
+ const url = 'https://localhost:8888/api/v1/virtualnode';
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ // Extract virtualNodes from the API response
+ const virtualNodes = data.virtualNodes;
+ console.log('API Result:', virtualNodes);
+
+ // Process the data and populate the dropdown
+ if (Array.isArray(virtualNodes)) {
+ const dropdown = document.getElementById('itemDropdown');
+ dropdown.innerHTML = '';
+ dropdown.innerHTML += '';
+
+ virtualNodes.forEach(item => {
+ // Display each item on the console
+ console.log('Item X500Name:', item.holdingIdentity.x500Name);
+ console.log('Item ShortHash:', item.holdingIdentity.shortHash);
+
+ // Create an option element and add it to the dropdown
+ const option = document.createElement('option');
+ option.value = item.holdingIdentity.shortHash;
+ option.text = item.holdingIdentity.x500Name;
+ dropdown.appendChild(option);
+ });
+
+ // Add event listener to the dropdown to detect changes
+ dropdown.addEventListener('change', function () {
+ selectedX500Name = this.value;
+ console.log('Selected ShortHash:', selectedX500Name);
+
+ // Call a function or update a variable based on the selected item
+ handleDropdownChange(selectedX500Name);
+ });
+
+ } else {
+ console.warn('API Result is not an array.');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Initialize the virtual nodes dropdown on page load
+getAllVirtualNodes();
+
+// function to handle dropdown change
+function handleDropdownChange(selectedX500Name) {
+ console.log('Handling dropdown change for Item ID:', selectedX500Name);
+ getSelectedVNode();
+ // Additional actions based on the selected item can be performed here
+}
+
+// Function to get the selected virtual node and display it
+function getSelectedVNode() {
+ const displayElement = document.getElementById('selectedX500Display');
+ displayElement.textContent = `Selected X500: ${selectedX500Name}`;
+}
+
+// Function to get all flow results based on the selected virtual node
+function getAllFlowResult() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ const flowStatusResponses = data.flowStatusResponses;
+ console.log('API Result:', flowStatusResponses);
+
+ // Convert the JSON object to a string for display
+ const jsonString = JSON.stringify(flowStatusResponses, null, 2);
+
+ // Display the JSON string in the result div
+ document.getElementById('idResutl').innerHTML = `
${jsonString}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a POST request with a request body
+async function postCallFlow() {
+ let postBtn = $('#postBtn');
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ // Change the button text to indicate loading
+ postBtn.html('Loading...');
+
+ try {
+ // Perform the POST request with the provided request body
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4=',
+ 'Content-Type': 'application/json'
+ },
+ body: `${document.getElementById('requestBody').value}`
+ });
+
+ // Parse the response as JSON
+ const data = await response.json();
+
+ if (!data.ok) {
+ console.log(data.status);
+
+ // Check if the response status is not OK (2xx range)
+ if (data.status == "409" || data.status == "400") {
+ let flowStatusResponse = "title: " + data.title + "\nStatus: " + data.status;
+
+ // Display additional details for 400 status
+ if (data.status == "400") {
+ flowStatusResponse += "\nDetails: \n Cause: " + data.details.cause + "\n Reason: " + data.details.reason;
+ }
+
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponse}
`;
+ return;
+ }
+ }
+
+ let msg = "null";
+ let typ = "null";
+
+ // Construct a string with the flow status responses
+ let flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponses}
`;
+ } catch (error) {
+ console.log('Error:', error);
+ } finally {
+ // Restore the button text after the operation is complete
+ postBtn.html('Post');
+ }
+}
+
+// Function to display an item on the page
+function displayItemOnPage(item) {
+ const resultDiv = document.getElementById('queryResult');
+
+ // Create a new element to display the item
+ const itemElement = document.createElement('div');
+ itemElement.innerHTML = `
+
QueryResult: ${item}
+ `;
+
+ // Append the new element to the result div
+ resultDiv.appendChild(itemElement);
+}
+
+// Function to open a specific tab by hiding/showing content
+function openTab(tabName) {
+ // Hide all tab content
+ var tabContents = document.getElementsByClassName("tab-content");
+ for (var i = 0; i < tabContents.length; i++) {
+ tabContents[i].style.display = "none";
+ document.getElementById(tabContents[i].id + "-tab").style.backgroundColor = "rgb(52,152,219)";
+ }
+
+ // Show the selected tab content
+ var selectedTab = document.getElementById(tabName);
+ if (selectedTab) {
+ selectedTab.style.display = "block";
+ document.getElementById(tabName + "-tab").style.backgroundColor = "rgb(173,216,230)";
+ }
+}
+
+// Function to display a flow for a specific virtual node
+function oneFlow() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}/${document.getElementById('clientID').value}`;
+
+ // Validate clientID input
+ if (document.getElementById('clientID').value == "") {
+ alert("Please input a clientId");
+ return;
+ }
+
+ // Perform a GET request to display a flow
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ let msg = "null";
+ let typ = "null";
+
+ // Check if the flow status is "FAILED" and extract error details
+ if (data.flowStatus == "FAILED") {
+ msg = data.flowError.message;
+ typ = data.flowError.type;
+ }
+
+ // Construct a string with the flow status responses
+ const flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('idResutl').innerHTML = `
${flowStatusResponses}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to determine which flow-related action to execute based on user input
+function executeButtonFlow() {
+ // Check the value of the dropdown to determine which action to perform
+ if (document.getElementById("dropdown").value == "option1") {
+ getAllFlowResult();
+ } else {
+ oneFlow();
+ }
+}
+
+function queryDropDownChange(){
+ if (document.getElementById("dropdown").value == "option1") {
+ document.getElementById("clientID").style.display = 'none';
+ }else{
+ document.getElementById("clientID").style.display = 'block';
+
+ }
+}
\ No newline at end of file
diff --git a/java-samples/sendAndRecieveTransaction/FlowManagementUI/static/css/main.css b/java-samples/sendAndRecieveTransaction/FlowManagementUI/static/css/main.css
new file mode 100644
index 0000000..5e4d849
--- /dev/null
+++ b/java-samples/sendAndRecieveTransaction/FlowManagementUI/static/css/main.css
@@ -0,0 +1,202 @@
+body {
+ font-family: 'Roboto', sans-serif;
+ background-color: #f4f4f4;
+ color: #333;
+ margin: 50px; /* Add margin to the entire body */
+ padding: 0;
+}
+
+h1 {
+ text-align: center;
+ color: #0e0c0c;
+}
+
+/* Style for the label */
+label {
+ display: block;
+ margin-bottom: 10px;
+ font-weight: bold;
+ color: #333;
+}
+/* Style for the dropdown button */
+select {
+ width: 20%;
+ padding: 8px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#itemDropdown {
+ width: 40%;
+ padding: 10px;
+ box-sizing: border-box;
+}
+
+#clientID{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#OneFlowButon{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+}
+
+#result {
+ margin-bottom: 10px;
+}
+
+/* Button styling */
+button {
+ background-color: #3498db;
+ color: #fff;
+ padding: 10px 25px;
+ font-size: 16px;
+ border: 2px;
+ border-radius: 15px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+button:hover {
+ background-color: #2980b9;
+}
+
+/* Tab styling */
+.tab {
+ list-style-type: none; /* Remove default list styles */
+ display: inline-block; /* Display tabs inline */
+ padding: 2px 00px; /* Add padding to the tabs */
+ margin: 0 1px; /* Add margin between tabs */
+ cursor: pointer; /* Change cursor to pointer on hover */
+}
+
+
+.tab li {
+ flex: 1;
+ text-align: center;
+ padding: 10px;
+ background-color: #3498db;
+ color: #fff;
+ border-radius: 8px 15px 0 0;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.tab li:hover {
+ background-color: #2980b9;
+}
+
+/* Tab content styling */
+.tab-content {
+ display: none;
+ padding: 20px;
+ border: 1px solid #3498db;
+ border-radius: 0 0 5px 5px;
+ background-color: #fff;
+}
+
+.styled-input {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 10px;
+ box-sizing: border-box;
+}
+
+.output {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ box-sizing: border-box;
+}
+
+#idResutl {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ margin-top: 10px;
+ height: 290px;
+ max-height: 290px; /* Set a maximum height for the scroll box */
+ overflow-y: auto; /* Enable vertical scrolling if content exceeds the box height */
+}
+
+/* Responsive design */
+@media screen and (max-width: 600px) {
+ .tab li {
+ border-radius: 5px;
+ margin-bottom: 5px;
+ }
+ .tab-content {
+ border-radius: 5px;
+ }
+}
+#call {
+ display: -ms-inline-flexbox;
+ flex-wrap: wrap;
+
+}
+
+/* Style for side-by-side input boxes */
+.flowcall-container {
+ display: flex;
+}
+
+.input-box, .text-box {
+ width: 150px; /* Set the desired width */
+ margin-right: 10px; /* Optional: Add margin for spacing between input boxes */
+ border-radius: 5px;
+ border: 1px solid #3498db;
+}
+
+.queryoption-container{
+ display: flex;
+}
+
+
+
+#requestBody {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+ margin-right: 10px; /* Add some margin between the input and button */
+}
+
+#postBtn {
+ flex: 0 0 auto; /* Don't allow the button to grow or shrink */
+ margin-top: 10px; /* Add some margin between the button and result box */
+ margin-right: 10px; /* Add some margin between the button and result box */
+ width: 500px; /* Set the desired width */
+ height: 50px; /* Set the desired height */
+}
+
+#queryResult {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+}
+.content-box{
+ padding: 10px; /* Add padding to the content boxes */
+}
+
+
diff --git a/java-samples/sendAndRecieveTransaction/FlowManagementUI/templates/index.html b/java-samples/sendAndRecieveTransaction/FlowManagementUI/templates/index.html
new file mode 100644
index 0000000..44df7ac
--- /dev/null
+++ b/java-samples/sendAndRecieveTransaction/FlowManagementUI/templates/index.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+ Flask Frontend Example
+
+
+
+
+
+
+
+
+
+
+
+
+
Flow Management APIs
+
+
+
+
+
+
+
+
Please select a flow initiator
+
+
+
+
+
Flow Call
+
Flow Query
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
diff --git a/java-samples/sendAndRecieveTransaction/README.md b/java-samples/sendAndRecieveTransaction/README.md
new file mode 100644
index 0000000..46f983b
--- /dev/null
+++ b/java-samples/sendAndRecieveTransaction/README.md
@@ -0,0 +1,120 @@
+# sendAndReceiveTransaction
+
+When working with the Corda platform, every transaction is stored in the participants'
+vaults. The vault is a where all the transactions involving the owner are securely saved.
+Each vault is unique and accessible only by its owner,
+serving as a ledger to track all the owner's transactions.
+However, there are scenarios where you may want a third party to receive a copy of the
+transaction. This is where the SendAndRecieveTransaction function becomes essential.
+For instance, if Alice conducts a transaction with Bob and wants Charlie to receive a copy,
+Alice can simply run a flow using the transaction ID to send a copy to Charlie's vault.
+Another application of this function can be an automated reporting tool,
+which can be utilized at the end of each transaction finalizing flow to automatically
+report to a specific vnode. This functionality can act like a bookkeeper,
+meticulously tracking each transaction and ensuring accurate record-keeping.
+`
+
+### Setting up
+
+1. We will begin our test deployment with clicking the `startCorda`. This task will load up the combined Corda workers in docker.
+ A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v5_2/swagger#/. You can test out some
+ functions to check connectivity. (GET /cpi function call should return an empty list as for now.)
+2. We will now deploy the cordapp with a click of `5-vNodeSetup` task. Upon successful deployment of the CPI, the GET /cpi function call should now return the meta data of the cpi you just upload
+
+
+
+### Running the app
+
+In Corda 5, flows will be triggered via `POST /flow/{holdingidentityshorthash}` and flow result will need to be view at `GET /flow/{holdingidentityshorthash}/{clientrequestid}`
+* holdingidentityshorthash: the id of the network participants, ie Bob, Alice, Charlie. You can view all the short hashes of the network member with another gradle task called `ListVNodes`
+* clientrequestid: the id you specify in the flow requestBody when you trigger a flow.
+
+#### Step 1: Create IOUState between two parties
+Pick a VNode identity to initiate the IOU creation, and get its short hash. (Let's pick Alice. Don't pick Bob because Bob is the person who alice will borrow from).
+
+Go to `POST /flow/{holdingidentityshorthash}`, enter the identity short hash(Alice's hash) and request body:
+```
+{
+ "clientRequestId": "createiou-1",
+ "flowClassName": "com.r3.developers.samples.obligation.workflows.IOUIssueFlow",
+ "requestBody": {
+ "amount":"20",
+ "lender":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB"
+ }
+}
+```
+
+After trigger the create-IOU flow, hop to `GET /flow/{holdingidentityshorthash}/{clientrequestid}` and enter the short hash(Alice's hash) and client request id to view the flow result
+The stateRef of the transaction will be returned as a result of the flow query. Which is the "flowResult".
+
+#### Step 2: Sending a copy of the transaction to a third party.
+If a member needs to share a copy of their transaction with another member,
+they can do so using the process outlined below. For instance,
+if Alice wishes to send a copy of the transaction to Dave,
+we can execute the following request body with her short hash:
+
+```
+{
+ "clientRequestId": "sendAndRecieve-1",
+ "flowClassName": "com.r3.developers.samples.obligation.workflows.sendAndRecieveTransactionFlow",
+ "requestBody": {
+ "stateRef": "STATEREF ID HERE",
+ "members": ["CN=Dave, OU=Test Dept, O=R3, L=London, C=GB"],
+ "forceBackchain": "false"
+ }
+}
+```
+
+Ensure to replace the stateRef variable with the stateRef of the transaction. The stateRef will be found when you run the GET call,
+QueryAll in the swaggerAPI. The stateRef is labeled as `flowResult` in response body, begins with SHA-256D:XXXXX..
+```
+[
+ {
+ "holdingIdentityShortHash": "A93A019B324E",
+ "clientRequestId": "createiou-1",
+ "flowId": "26ea3f95-141b-4aaa-9b58-e3a685dc54d3",
+ "flowStatus": "COMPLETED",
+ "flowResult": "SHA-256D:B3D87C8B446C277B5658BBB2A18DC7491539D898B70F074418878091AE315B4A",
+ "flowError": null,
+ "timestamp": "2024-07-08T04:37:16.175Z"
+ }
+]
+```
+After running this flow Dave will have the transaction in his vault.
+
+
+Results
+
+Currently, Alice has two transactions stored in her vault.
+The transaction we aim to send to Charlie is identified by the ID SHA-256D:8DFDD672….
+You can view Alice's transactions in the image provided below:
+
+
+
+
+
+On the other hand, Charlie’s vault currently holds no transactions,
+as illustrated in the image below:
+
+
+
+
+
+Once Alice executes the flow, Charlie’s vault is updated to include the transaction,
+which is displayed as follows:
+
+
+
+
+
+All images of the vault were sourced through DBeaver by establishing a connection
+to the Cordapp using PostgreSQL. The credentials utilized for this connection are as follows:
+
+POSTGRES_DB = cordacluster
+POSTGRES_USER = postgres
+POSTGRES_PASSWORD = password
+
+To access the vault, navigate through the hierarchy in
+PostgreSQL: Databases > cordacluster > Schemas > vnode_vault_(HASH_ID_OF_VNODE) >
+Tables > utxo_transaction. To view the transactions,
+simply double-click on utxo_transaction.
\ No newline at end of file
diff --git a/java-samples/sendAndRecieveTransaction/aliceVault.png b/java-samples/sendAndRecieveTransaction/aliceVault.png
new file mode 100644
index 0000000000000000000000000000000000000000..d377488d4b50cbdc78d2c8199dabf5fbad10b0e7
GIT binary patch
literal 17869
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