├── .gitignore ├── .idea ├── codeStyles │ └── codeStyleConfig.xml └── inspectionProfiles │ └── Project_Default.xml ├── LICENCE ├── README.md ├── TRADEMARK ├── build.gradle ├── config ├── dev │ └── log4j2.xml └── test │ └── log4j2.xml ├── cordapp-contracts-states ├── build.gradle └── src │ └── main │ └── kotlin │ └── net │ └── cordaclub │ └── marge │ └── StatesAndContracts.kt ├── cordapp ├── build.gradle └── src │ ├── integrationTest │ └── kotlin │ │ └── net │ │ └── cordaclub │ │ └── marge │ │ └── DriverBasedTest.kt │ ├── main │ ├── kotlin │ │ └── net │ │ │ └── cordaclub │ │ │ └── marge │ │ │ ├── App.kt │ │ │ ├── DemoService.kt │ │ │ ├── Initializer.kt │ │ │ ├── InsurerPaymentFlows.kt │ │ │ ├── PatientPaymentsFlow.kt │ │ │ ├── Patients.kt │ │ │ ├── RetrieveInsurerQuotesFlow.kt │ │ │ ├── TriggerTreatmentPaymentsFlow.kt │ │ │ ├── bank │ │ │ └── BankAPI.kt │ │ │ ├── hospital │ │ │ └── HospitalAPI.kt │ │ │ ├── insurer │ │ │ └── InsurerAPI.kt │ │ │ └── util │ │ │ ├── Futures.kt │ │ │ └── Queries.kt │ ├── resources │ │ ├── META-INF │ │ │ └── services │ │ │ │ ├── net.corda.core.serialization.SerializationWhitelist │ │ │ │ └── net.corda.webserver.services.WebServerPluginRegistry │ │ ├── log4j2.xml │ │ ├── templateWeb │ │ │ └── index.html │ │ └── web │ │ │ ├── .gitignore │ │ │ ├── bank │ │ │ ├── app.js │ │ │ ├── favicon.ico │ │ │ ├── img │ │ │ │ └── logo.82b9c7a5.png │ │ │ └── index.html │ │ │ ├── hospital │ │ │ ├── app.js │ │ │ ├── favicon.ico │ │ │ └── index.html │ │ │ └── insurer │ │ │ ├── app.js │ │ │ ├── favicon.ico │ │ │ ├── img │ │ │ └── logo.82b9c7a5.png │ │ │ └── index.html │ └── web │ │ ├── README.md │ │ ├── bank │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── README.md │ │ ├── babel.config.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ │ ├── favicon.ico │ │ │ └── index.html │ │ ├── src │ │ │ ├── App.vue │ │ │ ├── assets │ │ │ │ └── logo.png │ │ │ └── main.js │ │ └── vue.config.js │ │ ├── build.sh │ │ ├── hospital │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── README.md │ │ ├── babel.config.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ │ ├── favicon.ico │ │ │ └── index.html │ │ ├── src │ │ │ ├── App.vue │ │ │ ├── assets │ │ │ │ └── logo.png │ │ │ └── main.js │ │ └── vue.config.js │ │ ├── insurer │ │ ├── .browserslistrc │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .postcssrc.js │ │ ├── README.md │ │ ├── babel.config.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ │ ├── favicon.ico │ │ │ └── index.html │ │ ├── src │ │ │ ├── App.vue │ │ │ ├── assets │ │ │ │ └── logo.png │ │ │ └── main.js │ │ └── vue.config.js │ │ └── package-lock.json │ └── test │ ├── kotlin │ └── net │ │ └── cordaclub │ │ └── marge │ │ ├── ContractTests.kt │ │ ├── FlowTests.kt │ │ └── NodeDriver.kt │ └── resources │ └── log4j2.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lib ├── README.txt └── quasar.jar └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse, ctags, Mac metadata, log files 2 | .classpath 3 | .project 4 | .settings 5 | tags 6 | .DS_Store 7 | *.log 8 | *.log.gz 9 | *.orig 10 | 11 | .gradle 12 | 13 | # General build files 14 | **/build/* 15 | !docs/build/* 16 | 17 | lib/dokka.jar 18 | 19 | ### JetBrains template 20 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 21 | 22 | *.iml 23 | 24 | ## Directory-based project format: 25 | #.idea 26 | 27 | # if you remove the above rule, at least ignore the following: 28 | 29 | # Specific files to avoid churn 30 | .idea/*.xml 31 | .idea/copyright 32 | .idea/jsLibraryMappings.xml 33 | 34 | # User-specific stuff: 35 | .idea/tasks.xml 36 | .idea/dictionaries 37 | 38 | # Sensitive or high-churn files: 39 | .idea/dataSources.ids 40 | .idea/dataSources.xml 41 | .idea/sqlDataSources.xml 42 | .idea/dynamic.xml 43 | .idea/uiDesigner.xml 44 | 45 | # Gradle: 46 | .idea/libraries 47 | 48 | # Mongo Explorer plugin: 49 | .idea/mongoSettings.xml 50 | 51 | ## File-based project format: 52 | *.ipr 53 | *.iws 54 | 55 | ## Plugin-specific files: 56 | 57 | # IntelliJ 58 | /out/ 59 | cordapp/out/ 60 | cordapp-contracts-states/out/ 61 | 62 | # mpeltonen/sbt-idea plugin 63 | .idea_modules/ 64 | 65 | # JIRA plugin 66 | atlassian-ide-plugin.xml 67 | 68 | # Crashlytics plugin (for Android Studio and IntelliJ) 69 | com_crashlytics_export_strings.xml 70 | crashlytics.properties 71 | crashlytics-build.properties 72 | 73 | # docs related 74 | docs/virtualenv/ 75 | **/.vertx -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright 2016, R3 Limited. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Marge demo 2 | 3 | To run, start [NodeDriver](`./cordapp/src/test/kotlin/net/cordaclub/marge/NodeDriver.kt`) 4 | 5 | ## Build 6 | 7 | Make sure before committing changes, always rebuild the [web](cordapp/src/main/web/README.md) project. -------------------------------------------------------------------------------- /TRADEMARK: -------------------------------------------------------------------------------- 1 | Corda and the Corda logo are trademarks of R3CEV LLC and its affiliates. All rights reserved. 2 | 3 | For R3CEV LLC's trademark and logo usage information, please consult our Trademark Usage Policy at 4 | https://www.r3.com/trademark-policy/. 5 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.corda_release_group = 'net.corda' 3 | ext.corda_release_version = '3.2-corda' 4 | ext.corda_gradle_plugins_version = '3.1.0' 5 | ext.kotlin_version = '1.1.60' 6 | ext.junit_version = '4.12' 7 | ext.quasar_version = '0.7.9' 8 | 9 | repositories { 10 | // mavenLocal() 11 | mavenCentral() 12 | jcenter() 13 | } 14 | 15 | dependencies { 16 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 17 | classpath "net.corda.plugins:cordapp:$corda_gradle_plugins_version" 18 | classpath "net.corda.plugins:cordformation:$corda_gradle_plugins_version" 19 | classpath "net.corda.plugins:quasar-utils:$corda_gradle_plugins_version" 20 | } 21 | } 22 | 23 | repositories { 24 | mavenLocal() 25 | jcenter() 26 | mavenCentral() 27 | maven { url 'https://jitpack.io' } 28 | maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases' } 29 | } 30 | 31 | apply plugin: 'kotlin' 32 | apply plugin: 'net.corda.plugins.cordapp' 33 | apply plugin: 'net.corda.plugins.cordformation' 34 | apply plugin: 'net.corda.plugins.quasar-utils' 35 | 36 | sourceSets { 37 | main { 38 | resources { 39 | srcDir "config/dev" 40 | } 41 | } 42 | test { 43 | resources { 44 | srcDir "config/test" 45 | } 46 | } 47 | integrationTest { 48 | kotlin { 49 | compileClasspath += main.output + test.output 50 | runtimeClasspath += main.output + test.output 51 | srcDir file('src/integration-test/kotlin') 52 | } 53 | } 54 | } 55 | 56 | configurations { 57 | integrationTestCompile.extendsFrom testCompile 58 | integrationTestRuntime.extendsFrom testRuntime 59 | } 60 | 61 | dependencies { 62 | compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" 63 | testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" 64 | testCompile "junit:junit:$junit_version" 65 | 66 | // Corda integration dependencies 67 | cordaCompile "$corda_release_group:corda-core:$corda_release_version" 68 | cordaCompile "$corda_release_group:corda-finance:$corda_release_version" 69 | cordaCompile "$corda_release_group:corda-jackson:$corda_release_version" 70 | cordaCompile "$corda_release_group:corda-rpc:$corda_release_version" 71 | cordaCompile "$corda_release_group:corda-node-api:$corda_release_version" 72 | cordaCompile "$corda_release_group:corda-webserver-impl:$corda_release_version" 73 | cordaRuntime "$corda_release_group:corda:$corda_release_version" 74 | cordaRuntime "$corda_release_group:corda-webserver:$corda_release_version" 75 | 76 | testCompile "$corda_release_group:corda-node-driver:$corda_release_version" 77 | 78 | // CorDapp dependencies 79 | // Specify your CorDapp's dependencies below, including dependent CorDapps. 80 | // We've defined Cash as a dependent CorDapp as an example. 81 | cordapp project(":cordapp") 82 | cordapp project(":cordapp-contracts-states") 83 | cordapp "$corda_release_group:corda-finance:$corda_release_version" 84 | cordapp "io.cordite:dgl-cordapp:0.2.0" 85 | } 86 | 87 | task integrationTest(type: Test, dependsOn: []) { 88 | testClassesDirs = sourceSets.integrationTest.output.classesDirs 89 | classpath = sourceSets.integrationTest.runtimeClasspath 90 | } 91 | 92 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { 93 | kotlinOptions { 94 | languageVersion = "1.1" 95 | apiVersion = "1.1" 96 | jvmTarget = "1.8" 97 | javaParameters = true // Useful for reflection. 98 | } 99 | } 100 | 101 | task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { 102 | directory "./build/nodes" 103 | node { 104 | name "O=Notary,L=London,C=GB" 105 | notary = [validating: true] 106 | p2pPort 10002 107 | cordapps = [ 108 | "$project.group:cordapp-contracts-states:$project.version", 109 | "$project.group:cordapp:$project.version", 110 | "$corda_release_group:corda-finance:$corda_release_version", 111 | "io.cordite:dgl-cordapp:0.2.0" 112 | ] 113 | } 114 | node { 115 | name "O=Hospital,L=London,C=GB" 116 | p2pPort 10005 117 | rpcSettings { 118 | address("localhost:10006") 119 | adminAddress("localhost:10046") 120 | } 121 | webPort 10007 122 | cordapps = [ 123 | "$project.group:cordapp-contracts-states:$project.version", 124 | "$project.group:cordapp:$project.version", 125 | "$corda_release_group:corda-finance:$corda_release_version", 126 | "io.cordite:dgl-cordapp:0.2.0" 127 | ] 128 | rpcUsers = [[user: "user1", "password": "test", "permissions": ["ALL"]]] 129 | } 130 | node { 131 | name "O=Insurer-AIG,L=New York,C=US" 132 | p2pPort 10008 133 | rpcSettings { 134 | address("localhost:10009") 135 | adminAddress("localhost:10049") 136 | } 137 | webPort 10010 138 | cordapps = [ 139 | "$project.group:cordapp-contracts-states:$project.version", 140 | "$project.group:cordapp:$project.version", 141 | "$corda_release_group:corda-finance:$corda_release_version", 142 | "io.cordite:dgl-cordapp:0.2.0" 143 | ] 144 | rpcUsers = [[user: "user1", "password": "test", "permissions": ["ALL"]]] 145 | } 146 | node { 147 | name "O=Insurer-Vitality,L=New York,C=US" 148 | p2pPort 10011 149 | rpcSettings { 150 | address("localhost:10012") 151 | adminAddress("localhost:10052") 152 | } 153 | webPort 10013 154 | cordapps = [ 155 | "$project.group:cordapp-contracts-states:$project.version", 156 | "$project.group:cordapp:$project.version", 157 | "$corda_release_group:corda-finance:$corda_release_version", 158 | "io.cordite:dgl-cordapp:0.2.0" 159 | ] 160 | rpcUsers = [[user: "user1", "password": "test", "permissions": ["ALL"]]] 161 | } 162 | node { 163 | name "O=Bank,L=New York,C=US" 164 | p2pPort 10014 165 | rpcSettings { 166 | address("localhost:10015") 167 | adminAddress("localhost:10055") 168 | } 169 | webPort 10016 170 | cordapps = [ 171 | "$project.group:cordapp-contracts-states:$project.version", 172 | "$project.group:cordapp:$project.version", 173 | "$corda_release_group:corda-finance:$corda_release_version", 174 | "io.cordite:dgl-cordapp:0.2.0" 175 | ] 176 | rpcUsers = [[user: "user1", "password": "test", "permissions": ["ALL"]]] 177 | } 178 | } 179 | 180 | task runTemplateClient(type: JavaExec) { 181 | classpath = sourceSets.main.runtimeClasspath 182 | main = 'com.template.ClientKt' 183 | args 'localhost:10006' 184 | } 185 | -------------------------------------------------------------------------------- /config/dev/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | logs 6 | node-${hostName} 7 | ${log-path}/archive 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | %highlight{%level{length=1} %d{HH:mm:ss} %T %c{1}.%M - %msg%n}{INFO=white,WARN=red,FATAL=bright red blink} 17 | > 18 | 19 | 20 | 21 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /config/test/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | [%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n 8 | > 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /cordapp-contracts-states/build.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | mavenLocal() 3 | jcenter() 4 | mavenCentral() 5 | maven { url 'https://jitpack.io' } 6 | maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases' } 7 | } 8 | 9 | apply plugin: 'kotlin' 10 | apply plugin: 'net.corda.plugins.cordapp' 11 | apply plugin: 'net.corda.plugins.cordformation' 12 | 13 | sourceSets { 14 | main { 15 | resources { 16 | srcDir "../config/dev" 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" 23 | 24 | // Corda integration dependencies 25 | cordaCompile "$corda_release_group:corda-core:$corda_release_version" 26 | cordaCompile "$corda_release_group:corda-finance:$corda_release_version" 27 | cordaCompile "$corda_release_group:corda-jackson:$corda_release_version" 28 | cordaCompile "$corda_release_group:corda-rpc:$corda_release_version" 29 | cordaCompile "$corda_release_group:corda-node-api:$corda_release_version" 30 | cordaCompile "$corda_release_group:corda-webserver-impl:$corda_release_version" 31 | cordaRuntime "$corda_release_group:corda:$corda_release_version" 32 | cordaRuntime "$corda_release_group:corda-webserver:$corda_release_version" 33 | } 34 | 35 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { 36 | kotlinOptions { 37 | languageVersion = "1.1" 38 | apiVersion = "1.1" 39 | jvmTarget = "1.8" 40 | javaParameters = true // Useful for reflection. 41 | } 42 | } -------------------------------------------------------------------------------- /cordapp-contracts-states/src/main/kotlin/net/cordaclub/marge/StatesAndContracts.kt: -------------------------------------------------------------------------------- 1 | package net.cordaclub.marge 2 | 3 | import net.corda.core.contracts.* 4 | import net.corda.core.identity.AbstractParty 5 | import net.corda.core.identity.Party 6 | import net.corda.core.serialization.CordaSerializable 7 | import net.corda.core.transactions.LedgerTransaction 8 | import java.util.* 9 | 10 | @CordaSerializable 11 | data class Patient(val name: String, val nino: String) 12 | 13 | @CordaSerializable 14 | data class Treatment( 15 | val patient: Patient, 16 | val description: String, 17 | val hospital: Party 18 | ) 19 | 20 | /** 21 | * Initial request sent to insurers to get a coverage estimation 22 | */ 23 | @CordaSerializable 24 | data class TreatmentCoverageEstimation( 25 | val treatment: Treatment, 26 | val estimatedAmount: Amount 27 | ) 28 | 29 | @CordaSerializable 30 | data class InsurerQuote( 31 | val insurer: Party, 32 | val maxCoveredValue: Amount 33 | ) 34 | 35 | @CordaSerializable 36 | enum class TreatmentStatus { 37 | ESTIMATED, QUOTED, FINALISED, PARTIALLY_PAID, FULLY_PAID 38 | } 39 | 40 | /** 41 | * This is the main state that models a treatment from estimation to payment 42 | */ 43 | class TreatmentState( 44 | val treatment: Treatment, 45 | val estimatedTreatmentCost: Amount, 46 | val treatmentCost: Amount?, 47 | val amountPayed: Amount?, 48 | val insurerQuote: InsurerQuote?, 49 | val treatmentStatus: TreatmentStatus, 50 | override val linearId: UniqueIdentifier = UniqueIdentifier() 51 | ) : LinearState { 52 | override val participants: List 53 | get() = listOf(treatment.hospital) 54 | 55 | override fun toString(): String { 56 | return "TreatmentState(treatment=$treatment, treatmentCost=$treatmentCost)" 57 | } 58 | } 59 | 60 | sealed class TreatmentCommand : TypeOnlyCommandData() { 61 | class EstimateTreatment : TreatmentCommand() 62 | class QuoteTreatment : TreatmentCommand() 63 | class FinaliseTreatment : TreatmentCommand() 64 | class CollectInsurerPay : TreatmentCommand() 65 | class FullyPayTreatment : TreatmentCommand() 66 | } 67 | 68 | class TreatmentContract : Contract { 69 | companion object { 70 | val CONTRACT_ID: ContractClassName = TreatmentContract::class.qualifiedName!! 71 | } 72 | 73 | override fun verify(tx: LedgerTransaction) { 74 | val command = tx.commands.requireSingleCommand() 75 | val setOfSigners = command.signers.toSet() 76 | val outputTreatment = tx.outputsOfType().single() 77 | val inputTreatments = tx.inputsOfType() 78 | 79 | requireThat { 80 | inputTreatments.singleOrNull()?.let { 81 | "The same treatment" using (it.treatment == outputTreatment.treatment) 82 | "The same estimatedTreatmentCost" using (it.estimatedTreatmentCost == outputTreatment.estimatedTreatmentCost) 83 | "The same linearId" using (it.linearId == outputTreatment.linearId) 84 | } 85 | "The patient has a correct NINO" using (isCorrectNino(outputTreatment.treatment.patient.nino)) 86 | "The hospital signed the transaction" using (setOfSigners.containsAll(outputTreatment.participants.map { it.owningKey } + inputTreatments.flatMap { it.participants.map { it.owningKey } })) 87 | when (command.value) { 88 | is TreatmentCommand.EstimateTreatment -> { 89 | "No inputs should be consumed when estimating a treatment." using (inputTreatments.isEmpty()) 90 | "The output status is correct" using (outputTreatment.treatmentStatus == TreatmentStatus.ESTIMATED) 91 | } 92 | is TreatmentCommand.QuoteTreatment -> { 93 | "The input status is correct" using (inputTreatments.single().treatmentStatus == TreatmentStatus.ESTIMATED) 94 | "The output status is correct" using (outputTreatment.treatmentStatus == TreatmentStatus.QUOTED) 95 | "The insurer signed the transaction" using (setOfSigners.contains(outputTreatment.insurerQuote!!.insurer.owningKey)) 96 | "The estimated value is greater or equal than the quote" using (outputTreatment.estimatedTreatmentCost >= outputTreatment.insurerQuote!!.maxCoveredValue) 97 | } 98 | is TreatmentCommand.FinaliseTreatment -> { 99 | "The input status is correct" using (inputTreatments.single().treatmentStatus == TreatmentStatus.QUOTED) 100 | "The output status is correct" using (outputTreatment.treatmentStatus == TreatmentStatus.FINALISED) 101 | "The actual cost of treatment is set" using (outputTreatment.treatmentCost != null) 102 | } 103 | is TreatmentCommand.CollectInsurerPay -> { 104 | "The input status is correct" using (inputTreatments.single().treatmentStatus == TreatmentStatus.FINALISED) 105 | "The output status is correct" using (outputTreatment.treatmentStatus == TreatmentStatus.PARTIALLY_PAID) 106 | "The amount payed is less than the cost" using (outputTreatment.amountPayed!! <= outputTreatment.treatmentCost!!) 107 | "The amount payed is correct" using (outputTreatment.amountPayed!! == min(outputTreatment.insurerQuote!!.maxCoveredValue, outputTreatment.treatmentCost)) 108 | } 109 | is TreatmentCommand.FullyPayTreatment -> { 110 | "The input status is correct" using (inputTreatments.single().treatmentStatus == TreatmentStatus.PARTIALLY_PAID) 111 | "The output status is correct" using (outputTreatment.treatmentStatus == TreatmentStatus.FULLY_PAID) 112 | "There is nothing left to pay" using (outputTreatment.amountPayed!! == outputTreatment.treatmentCost!!) 113 | } 114 | } 115 | } 116 | } 117 | } 118 | 119 | 120 | // Utility 121 | fun > min(v1: T, v2: T): T = if (v1 < v2) v1 else v2 122 | 123 | fun isCorrectNino(nino: String): Boolean { 124 | //todo Yashwinee - see https://stackoverflow.com/questions/10204378/regular-expression-to-validate-uk-national-insurance-number 125 | return true 126 | } -------------------------------------------------------------------------------- /cordapp/build.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | // mavenLocal() 3 | jcenter() 4 | mavenCentral() 5 | maven { url 'https://jitpack.io' } 6 | maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases' } 7 | } 8 | 9 | apply plugin: 'kotlin' 10 | apply plugin: 'net.corda.plugins.cordapp' 11 | apply plugin: 'net.corda.plugins.cordformation' 12 | apply plugin: 'net.corda.plugins.quasar-utils' 13 | 14 | sourceSets { 15 | main { 16 | resources { 17 | srcDir "config/dev" 18 | } 19 | } 20 | test { 21 | resources { 22 | srcDir "config/test" 23 | } 24 | } 25 | integrationTest { 26 | kotlin { 27 | compileClasspath += main.output + test.output 28 | runtimeClasspath += main.output + test.output 29 | srcDir file('src/integration-test/kotlin') 30 | } 31 | } 32 | } 33 | 34 | configurations { 35 | integrationTestCompile.extendsFrom testCompile 36 | integrationTestRuntime.extendsFrom testRuntime 37 | } 38 | 39 | dependencies { 40 | compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" 41 | testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" 42 | testCompile "junit:junit:$junit_version" 43 | 44 | // Corda integration dependencies 45 | cordaCompile "$corda_release_group:corda-core:$corda_release_version" 46 | cordaCompile "$corda_release_group:corda-finance:$corda_release_version" 47 | cordaCompile "$corda_release_group:corda-jackson:$corda_release_version" 48 | cordaCompile "$corda_release_group:corda-rpc:$corda_release_version" 49 | cordaCompile "$corda_release_group:corda-node-api:$corda_release_version" 50 | cordaCompile "$corda_release_group:corda-webserver-impl:$corda_release_version" 51 | cordaRuntime "$corda_release_group:corda:$corda_release_version" 52 | cordaRuntime "$corda_release_group:corda-webserver:$corda_release_version" 53 | 54 | testCompile "$corda_release_group:corda-node-driver:$corda_release_version" 55 | 56 | // CorDapp dependencies 57 | // Specify your CorDapp's dependencies below, including dependent CorDapps. 58 | cordapp project(":cordapp-contracts-states") 59 | compile "io.bluebank.braid:braid-corda:3.2.0" 60 | cordapp "io.cordite:dgl-cordapp:0.2.0" 61 | } 62 | 63 | task integrationTest(type: Test, dependsOn: []) { 64 | testClassesDirs = sourceSets.integrationTest.output.classesDirs 65 | classpath = sourceSets.integrationTest.runtimeClasspath 66 | } 67 | 68 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { 69 | kotlinOptions { 70 | languageVersion = "1.1" 71 | apiVersion = "1.1" 72 | jvmTarget = "1.8" 73 | javaParameters = true // Useful for reflection. 74 | } 75 | } 76 | 77 | /** 78 | * we will disable all the npm tasks to speed up the build 79 | * when editing the web source, please make sure that they are built before committing 80 | 81 | processResources { 82 | dependsOn 'npmBuildInsurer' 83 | dependsOn 'npmBuildHospital' 84 | dependsOn 'npmBuildBank' 85 | } 86 | 87 | clean { 88 | dependsOn 'npmCleanInsurer' 89 | dependsOn 'npmCleanHospital' 90 | dependsOn 'npmCleanBank' 91 | } 92 | 93 | task npmCleanInsurer(type: Exec) { 94 | dependsOn 'npmInstallInsurer' 95 | workingDir 'src/main/web/insurer' 96 | commandLine 'npm', 'run', 'clean' 97 | } 98 | 99 | task npmCleanHospital(type: Exec) { 100 | dependsOn 'npmInstallHospital' 101 | workingDir 'src/main/web/hospital' 102 | commandLine 'npm', 'run', 'clean' 103 | } 104 | 105 | task npmCleanBank(type: Exec) { 106 | dependsOn 'npmInstallBank' 107 | workingDir 'src/main/web/bank' 108 | commandLine 'npm', 'run', 'clean' 109 | } 110 | 111 | 112 | task npmBuildInsurer(type: Exec) { 113 | dependsOn 'npmInstallInsurer' 114 | workingDir 'src/main/web/insurer' 115 | commandLine 'npm', 'run', 'build' 116 | } 117 | 118 | task npmBuildHospital(type: Exec) { 119 | dependsOn 'npmInstallHospital' 120 | workingDir 'src/main/web/hospital' 121 | commandLine 'npm', 'run', 'build' 122 | } 123 | 124 | task npmBuildBank(type: Exec) { 125 | dependsOn 'npmInstallBank' 126 | workingDir 'src/main/web/bank' 127 | commandLine 'npm', 'run', 'build' 128 | } 129 | 130 | task npmInstallInsurer(type: Exec) { 131 | workingDir 'src/main/web/insurer' 132 | commandLine 'npm', 'install' 133 | } 134 | 135 | task npmInstallHospital(type: Exec) { 136 | workingDir 'src/main/web/hospital' 137 | commandLine 'npm', 'install' 138 | } 139 | 140 | task npmInstallBank(type: Exec) { 141 | workingDir 'src/main/web/bank' 142 | commandLine 'npm', 'install' 143 | } 144 | 145 | npmInstallBank.onlyIf { 146 | !(new File('src/main/web/bank/node_modules')).exists() 147 | } 148 | 149 | npmInstallInsurer.onlyIf { 150 | !(new File('src/main/web/insurer/node_modules')).exists() 151 | } 152 | 153 | npmInstallHospital.onlyIf { 154 | !(new File('src/main/web/hospital/node_modules')).exists() 155 | } 156 | 157 | */ -------------------------------------------------------------------------------- /cordapp/src/integrationTest/kotlin/net/cordaclub/marge/DriverBasedTest.kt: -------------------------------------------------------------------------------- 1 | package net.cordaclub.marge 2 | 3 | import net.corda.core.identity.CordaX500Name 4 | import net.corda.core.utilities.getOrThrow 5 | import net.corda.testing.core.TestIdentity 6 | import net.corda.testing.driver.DriverDSL 7 | import net.corda.testing.driver.DriverParameters 8 | import net.corda.testing.driver.NodeHandle 9 | import net.corda.testing.driver.driver 10 | import okhttp3.OkHttpClient 11 | import okhttp3.Request 12 | import org.junit.Test 13 | import java.util.concurrent.Future 14 | import kotlin.test.assertEquals 15 | 16 | class DriverBasedTest { 17 | private val bankA = TestIdentity(CordaX500Name("BankA", "", "GB")) 18 | private val bankB = TestIdentity(CordaX500Name("BankB", "", "US")) 19 | 20 | @Test 21 | fun `node test`() = withDriver { 22 | // Start a pair of nodes and wait for them both to be ready. 23 | val (partyAHandle, partyBHandle) = startNodes(bankA, bankB) 24 | 25 | // From each node, make an RPC call to retrieve another node's name from the network map, to verify that the 26 | // nodes have started and can communicate. 27 | 28 | // This is a very basic test: in practice tests would be starting flows, and verifying the states in the vault 29 | // and other important metrics to ensure that your CorDapp is working as intended. 30 | assertEquals(bankB.name, partyAHandle.resolveName(bankB.name)) 31 | assertEquals(bankA.name, partyBHandle.resolveName(bankA.name)) 32 | } 33 | 34 | @Test 35 | fun `node webserver test`() = withDriver { 36 | // This test starts each node's webserver and makes an HTTP call to retrieve the body of a GET endpoint on 37 | // the node's webserver, to verify that the nodes' webservers have started and have loaded the API. 38 | startWebServers(bankA, bankB).forEach { 39 | val request = Request.Builder() 40 | .url("http://${it.listenAddress}/api/template/templateGetEndpoint") 41 | .build() 42 | val response = OkHttpClient().newCall(request).execute() 43 | 44 | assertEquals("Template GET endpoint.", response.body().string()) 45 | } 46 | } 47 | 48 | // region Utility functions 49 | 50 | // Runs a test inside the Driver DSL, which provides useful functions for starting nodes, etc. 51 | private fun withDriver(test: DriverDSL.() -> Unit) = driver( 52 | DriverParameters(isDebug = true, startNodesInProcess = true) 53 | ) { test() } 54 | 55 | // Makes an RPC call to retrieve another node's name from the network map. 56 | private fun NodeHandle.resolveName(name: CordaX500Name) = rpc.wellKnownPartyFromX500Name(name)!!.name 57 | 58 | // Resolves a list of futures to a list of the promised values. 59 | private fun List>.waitForAll(): List = map { it.getOrThrow() } 60 | 61 | // Starts multiple nodes simultaneously, then waits for them all to be ready. 62 | private fun DriverDSL.startNodes(vararg identities: TestIdentity) = identities 63 | .map { startNode(providedName = it.name) } 64 | .waitForAll() 65 | 66 | // Starts multiple webservers simultaneously, then waits for them all to be ready. 67 | private fun DriverDSL.startWebServers(vararg identities: TestIdentity) = startNodes(*identities) 68 | .map { startWebserver(it) } 69 | .waitForAll() 70 | 71 | // endregion 72 | } -------------------------------------------------------------------------------- /cordapp/src/main/kotlin/net/cordaclub/marge/App.kt: -------------------------------------------------------------------------------- 1 | package net.cordaclub.marge 2 | 3 | import co.paralleluniverse.fibers.Suspendable 4 | import net.corda.core.flows.* 5 | import net.corda.core.messaging.CordaRPCOps 6 | import net.corda.core.serialization.SerializationWhitelist 7 | import net.corda.webserver.services.WebServerPluginRegistry 8 | import java.util.function.Function 9 | import javax.ws.rs.GET 10 | import javax.ws.rs.Path 11 | import javax.ws.rs.Produces 12 | import javax.ws.rs.core.MediaType 13 | import javax.ws.rs.core.Response 14 | 15 | // ***************** 16 | // * API Endpoints * 17 | // ***************** 18 | @Path("template") 19 | class TemplateApi(val rpcOps: CordaRPCOps) { 20 | // Accessible at /api/template/templateGetEndpoint. 21 | @GET 22 | @Path("templateGetEndpoint") 23 | @Produces(MediaType.APPLICATION_JSON) 24 | fun templateGetEndpoint(): Response { 25 | return Response.ok("Template GET endpoint.").build() 26 | } 27 | } 28 | 29 | // ********* 30 | // * Flows * 31 | // ********* 32 | @InitiatingFlow 33 | @StartableByRPC 34 | class Initiator : FlowLogic() { 35 | @Suspendable 36 | override fun call() { 37 | // Flow implementation goes here 38 | } 39 | } 40 | 41 | @InitiatedBy(Initiator::class) 42 | class Responder(val counterpartySession: FlowSession) : FlowLogic() { 43 | @Suspendable 44 | override fun call() { 45 | // Flow implementation goes here 46 | } 47 | } 48 | 49 | // *********** 50 | // * Plugins * 51 | // *********** 52 | class TemplateWebPlugin : WebServerPluginRegistry { 53 | // A list of lambdas that create objects exposing web JAX-RS REST APIs. 54 | override val webApis: List> = listOf(Function(::TemplateApi)) 55 | //A list of directories in the resources directory that will be served by Jetty under /web. 56 | // This template's web frontend is accessible at /web/template. 57 | override val staticServeDirs: Map = mapOf( 58 | // This will serve the templateWeb directory in resources to /web/template 59 | "template" to javaClass.classLoader.getResource("templateWeb").toExternalForm() 60 | ) 61 | } 62 | 63 | // Serialization whitelist. 64 | class TemplateSerializationWhitelist : SerializationWhitelist { 65 | override val whitelist: List> = listOf(TemplateData::class.java) 66 | } 67 | 68 | // This class is not annotated with @CordaSerializable, so it must be added to the serialization whitelist, above, if 69 | // we want to send it to other nodes within a flow. 70 | data class TemplateData(val payload: String) 71 | -------------------------------------------------------------------------------- /cordapp/src/main/kotlin/net/cordaclub/marge/DemoService.kt: -------------------------------------------------------------------------------- 1 | package net.cordaclub.marge 2 | 3 | import io.bluebank.braid.corda.BraidConfig 4 | import io.bluebank.braid.corda.router.Routers 5 | import io.cordite.dgl.corda.impl.LedgerApiImpl 6 | import io.vertx.core.Vertx 7 | import io.vertx.core.http.HttpServerOptions 8 | import io.vertx.ext.web.handler.StaticHandler 9 | import net.corda.core.node.AppServiceHub 10 | import net.corda.core.node.NodeInfo 11 | import net.corda.core.node.services.CordaService 12 | import net.corda.core.serialization.SingletonSerializeAsToken 13 | import net.corda.core.utilities.loggerFor 14 | import net.cordaclub.marge.Insurers.allInsurers 15 | import net.cordaclub.marge.bank.BankAPI 16 | import net.cordaclub.marge.hospital.HospitalAPI 17 | import net.cordaclub.marge.insurer.InsurerAPI 18 | import java.awt.Desktop 19 | import java.net.URI 20 | 21 | @CordaService 22 | class DemoService(private val serviceHub: AppServiceHub) : SingletonSerializeAsToken() { 23 | companion object { 24 | private val log = loggerFor() 25 | } 26 | 27 | //this is an ugly hack 28 | lateinit var service: Initializer 29 | 30 | private val vertx = Vertx.vertx() 31 | private var port: Int = 0 32 | private val ledger = LedgerApiImpl(serviceHub) 33 | 34 | init { 35 | log.info("Starting DemoService for ${serviceHub.myInfo.legalIdentities.first().name.organisation}") 36 | serviceHub.myInfo.apply { 37 | when { 38 | this.isOfNodeType("insurer") -> configureInsurer() 39 | this.isOfNodeType("bank") -> configureBank() 40 | this.isOfNodeType("hospital") -> configureHospital() 41 | else -> configureOtherNode() 42 | } 43 | } 44 | } 45 | 46 | private fun configureHospital() { 47 | service = HospitalAPI(serviceHub) 48 | val name = serviceHub.myInfo.legalIdentities.first().name 49 | port = 9000 + name.organisation.hashCode() % 2 50 | log.info("Starting Hospital $name on port http://localhost:$port") 51 | val static = StaticHandler.create("web/hospital").setCachingEnabled(false) 52 | val router = Routers.create(vertx, port) 53 | router.get("/*").order(10000).handler(static) 54 | BraidConfig() 55 | .withVertx(vertx) 56 | .withPort(port) 57 | .withHttpServerOptions(HttpServerOptions().setSsl(false)) 58 | .withService("hospital", service) 59 | .withService("ledger", ledger) 60 | .bootstrapBraid(serviceHub) 61 | } 62 | 63 | private fun configureBank() { 64 | service = BankAPI(serviceHub, Patients.allPatients, ledger) 65 | val name = serviceHub.myInfo.legalIdentities.first().name 66 | port = 7000 + name.organisation.hashCode() % 2 67 | log.info("Starting Bank $name on port http://localhost:$port") 68 | val static = StaticHandler.create("web/bank").setCachingEnabled(false) 69 | val router = Routers.create(vertx, port) 70 | router.get("/*").order(10000).handler(static) 71 | BraidConfig() 72 | .withVertx(vertx) 73 | .withPort(port) 74 | .withHttpServerOptions(HttpServerOptions().setSsl(false)) 75 | .withService("bank", service) 76 | .withService("ledger", ledger) 77 | .bootstrapBraid(serviceHub) 78 | } 79 | 80 | private fun configureInsurer() { 81 | service = InsurerAPI(serviceHub) 82 | val name = serviceHub.myInfo.legalIdentities.first().name 83 | port = 8000 + allInsurers.indexOf(name) + name.organisation.hashCode() % 2 84 | log.info("Starting Insurer $name on port http://localhost:$port") 85 | val static = StaticHandler.create("web/insurer").setCachingEnabled(false) 86 | val router = Routers.create(vertx, port) 87 | router.get("/*").order(10000).handler(static) 88 | BraidConfig() 89 | .withVertx(vertx) 90 | .withPort(port) 91 | .withHttpServerOptions(HttpServerOptions().setSsl(false)) 92 | .withService("insurer", service) 93 | .withService("ledger", ledger) 94 | .bootstrapBraid(serviceHub) 95 | } 96 | 97 | fun openWebPage() { 98 | Desktop.getDesktop().browse(URI("http://localhost:$port")) 99 | } 100 | 101 | private fun NodeInfo.isOfNodeType(name: String): Boolean { 102 | return legalIdentities.any { it.name.organisation.contains(name, true) } 103 | } 104 | 105 | private fun configureOtherNode() { 106 | log.info("unknown node type for ${serviceHub.myInfo.legalIdentities.first().name.organisation} - not configuring any services") 107 | } 108 | } -------------------------------------------------------------------------------- /cordapp/src/main/kotlin/net/cordaclub/marge/Initializer.kt: -------------------------------------------------------------------------------- 1 | package net.cordaclub.marge 2 | 3 | import io.vertx.core.Future 4 | 5 | abstract class Initializer { 6 | protected var initialised = false 7 | 8 | fun isInitialised(): Boolean { 9 | return initialised 10 | } 11 | 12 | abstract fun initialiseDemo(): Future 13 | } -------------------------------------------------------------------------------- /cordapp/src/main/kotlin/net/cordaclub/marge/InsurerPaymentFlows.kt: -------------------------------------------------------------------------------- 1 | package net.cordaclub.marge 2 | 3 | import co.paralleluniverse.fibers.Suspendable 4 | import io.cordite.dgl.corda.account.AccountAddress 5 | import io.cordite.dgl.corda.account.GetAccountFlow 6 | import io.cordite.dgl.corda.token.TokenTransactionSummary 7 | import io.cordite.dgl.corda.token.TokenType 8 | import io.cordite.dgl.corda.token.flows.TransferTokenSenderFunctions.Companion.prepareTokenMoveWithSummary 9 | import net.corda.core.contracts.Amount 10 | import net.corda.core.contracts.Command 11 | import net.corda.core.contracts.StateAndRef 12 | import net.corda.core.contracts.requireThat 13 | import net.corda.core.flows.* 14 | import net.corda.core.identity.Party 15 | import net.corda.core.node.ServiceHub 16 | import net.corda.core.transactions.SignedTransaction 17 | import net.corda.core.transactions.TransactionBuilder 18 | import net.corda.core.utilities.unwrap 19 | import net.cordaclub.marge.bank.BankAPI.Companion.TOKEN_SYMBOL 20 | import net.cordaclub.marge.insurer.InsurerAPI 21 | import java.util.* 22 | 23 | object InsurerFlows { 24 | 25 | /** 26 | * Triggered by the hospital to collect money from the insurer 27 | */ 28 | @InitiatingFlow 29 | class InsurerTreatmentPaymentFlow(private val treatmentState: StateAndRef, private val hospitalAccount: AccountAddress) : FlowLogic() { 30 | 31 | @Suspendable 32 | override fun call(): SignedTransaction { 33 | val treatment = treatmentState.state.data 34 | 35 | // create a tx with the insurer to (partially) settle the cost of the treatment 36 | val insurerSession = initiateFlow(treatment.insurerQuote!!.insurer) 37 | 38 | subFlow(SendStateAndRefFlow(insurerSession, listOf(treatmentState))) 39 | 40 | // send the [InsurerPaymentPayload] and receive the transaction containing the payment 41 | insurerSession.send(hospitalAccount) 42 | 43 | val tx = subFlow(object : SignTransactionFlow(insurerSession) { 44 | override fun checkTransaction(stx: SignedTransaction) = requireThat { 45 | val treatmentState = stx.coreTransaction.outputsOfType().single() 46 | stx.verify(serviceHub, checkSufficientSignatures = false) 47 | "The payment is correct." using (treatmentState.amountPayed!! <= treatmentState.treatmentCost!!) 48 | "The money was actually payed" using (stx.getTokenSummary().find { it.accountAddress == hospitalAccount }!!.quantity == treatmentState.amountPayed!!.quantity) 49 | } 50 | }) 51 | return waitForLedgerCommit(tx.id) 52 | } 53 | } 54 | 55 | /** 56 | * Runs on the insurer node. 57 | */ 58 | @InitiatedBy(InsurerTreatmentPaymentFlow::class) 59 | class InsurerTreatmentPaymentResponseFlow(private val session: FlowSession) : FlowLogic() { 60 | @Suspendable 61 | override fun call(): SignedTransaction { 62 | val hospital = session.counterparty 63 | val notary = serviceHub.networkMapCache.notaryIdentities.first() 64 | 65 | val treatmentState = subFlow(ReceiveStateAndRefFlow(session)).single() 66 | val hospitalAccount = session.receive().unwrap { it } 67 | 68 | // The amount to be paid by the insurer 69 | val treatment = treatmentState.state.data 70 | val insuranceAmount = min(treatment.treatmentCost!!, treatment.insurerQuote!!.maxCoveredValue) 71 | 72 | //Build transaction 73 | val txb = TransactionBuilder(notary).apply { 74 | addCommand(Command(TreatmentCommand.CollectInsurerPay(), listOf(hospital.owningKey, ourIdentity.owningKey))) 75 | addInputState(treatmentState) 76 | addOutputState(treatmentState.state.copy(data = treatment.let { 77 | TreatmentState( 78 | treatment = it.treatment, 79 | estimatedTreatmentCost = it.estimatedTreatmentCost, 80 | treatmentCost = it.treatmentCost, 81 | amountPayed = insuranceAmount, 82 | insurerQuote = it.insurerQuote, 83 | treatmentStatus = TreatmentStatus.PARTIALLY_PAID, 84 | linearId = it.linearId 85 | ) 86 | })) 87 | } 88 | 89 | // select insuranceAmount tokens from the insurer account and pay them to the hospital 90 | val insurerAccount = subFlow(GetAccountFlow(InsurerAPI.INSURER_ACCOUNT)).state.data 91 | prepareTokenMoveWithSummary(txb, insurerAccount.address, hospitalAccount, insuranceAmount.toToken(getBank(serviceHub)), serviceHub, ourIdentity, "Payment for treatment $treatment") 92 | 93 | val stx = serviceHub.signInitialTransaction(txb) // insurer signs the transaction 94 | val fullySignedTransaction = subFlow(CollectSignaturesFlow(stx, listOf(session))) 95 | 96 | val result = subFlow(FinalityFlow(fullySignedTransaction)) 97 | println("Finished InsurerTreatmentPaymentResponseFlow") 98 | return result 99 | } 100 | } 101 | 102 | } 103 | 104 | fun getBank(serviceHub: ServiceHub) = serviceHub.networkMapCache.allNodes.find { node -> 105 | node.legalIdentities.first().name.organisation.toLowerCase().contains("bank") 106 | }!!.legalIdentities.first() 107 | 108 | 109 | fun Amount.toToken(issuer: Party): Amount = Amount( 110 | quantity = this.quantity, 111 | displayTokenSize = this.displayTokenSize, 112 | token = TokenType.Descriptor(TOKEN_SYMBOL, 2, issuer.name)) 113 | 114 | fun SignedTransaction.getTokenSummary() = this.coreTransaction.outRefsOfType().single().state.data.amounts 115 | -------------------------------------------------------------------------------- /cordapp/src/main/kotlin/net/cordaclub/marge/PatientPaymentsFlow.kt: -------------------------------------------------------------------------------- 1 | package net.cordaclub.marge 2 | 3 | import co.paralleluniverse.fibers.Suspendable 4 | import io.cordite.dgl.corda.account.AccountAddress 5 | import io.cordite.dgl.corda.account.GetAccountFlow 6 | import io.cordite.dgl.corda.token.flows.TransferTokenSenderFunctions.Companion.prepareTokenMoveWithSummary 7 | import net.corda.core.contracts.Command 8 | import net.corda.core.contracts.requireThat 9 | import net.corda.core.flows.* 10 | import net.corda.core.transactions.SignedTransaction 11 | import net.corda.core.transactions.TransactionBuilder 12 | import net.corda.core.utilities.unwrap 13 | 14 | object PatientFlows { 15 | 16 | /** 17 | * Triggered by the hospital to collect money from the patient's bank 18 | */ 19 | @InitiatingFlow 20 | class PatientTreatmentPaymentFlow(private val paymentFromInsurerTx: SignedTransaction, private val hospitalAccount: AccountAddress) : FlowLogic() { 21 | @Suspendable 22 | override fun call(): SignedTransaction { 23 | // create a tx with the patient's bank to settle the rest 24 | val bankSession = initiateFlow(getBank(serviceHub)) 25 | val treatmentState = paymentFromInsurerTx.coreTransaction.outRefsOfType().first() 26 | subFlow(SendStateAndRefFlow(bankSession, listOf(treatmentState))) 27 | bankSession.send(hospitalAccount) 28 | 29 | val tx = subFlow(object : SignTransactionFlow(bankSession) { 30 | override fun checkTransaction(stx: SignedTransaction) = requireThat { 31 | val treatment = stx.coreTransaction.outputsOfType().single() 32 | stx.verify(serviceHub, checkSufficientSignatures = false) 33 | "The treatment is payed in full." using (treatment.amountPayed!! == treatment.treatmentCost!!) 34 | val patientAmount = (treatment.treatmentCost!! - treatment.insurerQuote!!.maxCoveredValue).quantity 35 | "The money was actually payed" using (stx.getTokenSummary().find { it.accountAddress == hospitalAccount }!!.quantity == if (patientAmount > 0) patientAmount else 0) 36 | } 37 | }) 38 | return waitForLedgerCommit(tx.id) 39 | } 40 | } 41 | 42 | /** 43 | * This is run by the bank 44 | */ 45 | @InitiatedBy(PatientTreatmentPaymentFlow::class) 46 | class PatientTreatmentPaymentResponseFlow(private val session: FlowSession) : FlowLogic() { 47 | @Suspendable 48 | override fun call(): SignedTransaction { 49 | val hospital = session.counterparty 50 | 51 | val treatmentState = subFlow(ReceiveStateAndRefFlow(session)).single() 52 | 53 | val hospitalAccount = session.receive().unwrap { it } 54 | 55 | val treatment = treatmentState.state.data 56 | val toPay = treatment.treatmentCost!! - treatment.amountPayed!! 57 | 58 | //Build transaction 59 | val notary = serviceHub.networkMapCache.notaryIdentities.first() 60 | val txb = TransactionBuilder(notary).apply { 61 | addCommand(Command(TreatmentCommand.FullyPayTreatment(), listOf(hospital.owningKey, ourIdentity.owningKey))) 62 | addInputState(treatmentState) 63 | addOutputState(treatmentState.state.copy(data = treatmentState.state.data.let { 64 | TreatmentState( 65 | treatment = it.treatment, 66 | estimatedTreatmentCost = it.estimatedTreatmentCost, 67 | treatmentCost = it.treatmentCost, 68 | amountPayed = it.treatmentCost, 69 | insurerQuote = it.insurerQuote, 70 | treatmentStatus = TreatmentStatus.FULLY_PAID, 71 | linearId = it.linearId 72 | ) 73 | })) 74 | } 75 | 76 | // select treatmentCost tokens from the patient account and pay them to the hospital 77 | val patientAccount = subFlow(GetAccountFlow(treatment.treatment.patient.name)).state.data 78 | prepareTokenMoveWithSummary(txb, patientAccount.address, hospitalAccount, toPay.toToken(getBank(serviceHub)), serviceHub, ourIdentity, "pay for treatment $treatment") 79 | 80 | val stx = serviceHub.signInitialTransaction(txb) // insurer signs the transaction 81 | val fullySignedTransaction = subFlow(CollectSignaturesFlow(stx, listOf(session))) 82 | val tx = subFlow(FinalityFlow(fullySignedTransaction)) 83 | println("Finished PatientTreatmentPaymentResponseFlow") 84 | return tx 85 | } 86 | } 87 | 88 | } 89 | 90 | -------------------------------------------------------------------------------- /cordapp/src/main/kotlin/net/cordaclub/marge/Patients.kt: -------------------------------------------------------------------------------- 1 | package net.cordaclub.marge 2 | 3 | import net.corda.core.identity.CordaX500Name 4 | 5 | object Patients { 6 | // Tudor - how about a selection from https://github.com/moby/moby/blob/master/pkg/namesgenerator/names-generator.go 7 | val allPatients = listOf( 8 | Patient("Joan Clarke", "ab123456b"), 9 | Patient("Seymour Roger Cray", "ab123456c"), 10 | Patient("Dorothy Vaughan", "ab123456d"), 11 | Patient("Steve Wozniak", "ab123456e") 12 | ) 13 | } 14 | 15 | object Insurers { 16 | val allInsurers = listOf( 17 | CordaX500Name("General Insurer", "Delhi", "IN"), 18 | CordaX500Name("Frugal Insurer", "Tokyo", "JP") 19 | ) 20 | } -------------------------------------------------------------------------------- /cordapp/src/main/kotlin/net/cordaclub/marge/RetrieveInsurerQuotesFlow.kt: -------------------------------------------------------------------------------- 1 | package net.cordaclub.marge 2 | 3 | import co.paralleluniverse.fibers.Suspendable 4 | import net.corda.core.contracts.Amount 5 | import net.corda.core.contracts.Command 6 | import net.corda.core.contracts.requireThat 7 | import net.corda.core.flows.* 8 | import net.corda.core.identity.Party 9 | import net.corda.core.serialization.CordaSerializable 10 | import net.corda.core.transactions.SignedTransaction 11 | import net.corda.core.transactions.TransactionBuilder 12 | import net.corda.core.utilities.loggerFor 13 | import net.corda.core.utilities.seconds 14 | import net.corda.core.utilities.unwrap 15 | import java.util.* 16 | 17 | object InsurerQuotingFlows { 18 | 19 | /** 20 | * First flow! 21 | * This is run by the Hospital when starting a treatment. 22 | * 23 | * 1) Creates the [TreatmentState] state that will evolve as the real treatment progresses. 24 | * 2) Requests quotes from Insurers. 25 | * 3) Selects the best quote and binds it in a transaction with the insurer. 26 | */ 27 | @StartableByRPC 28 | @StartableByService 29 | @InitiatingFlow 30 | class RetrieveInsurerQuotesFlow(private val treatmentCoverageEstimation: TreatmentCoverageEstimation, private val insurers: List) : FlowLogic() { 31 | 32 | companion object { 33 | private val log = loggerFor() 34 | } 35 | 36 | @Suspendable 37 | override fun call(): SignedTransaction { 38 | 39 | // Create and sign the Treatment State that will be used to justify the redemption of the Quote, and payment from the patient. 40 | val issueTreatmentTx = estimateTreatmentState() 41 | 42 | sleep(10.seconds) 43 | // Collect quotes from each insurer and select the best for the committed quote. 44 | val quotes = insurers.map { insurer -> 45 | // set up flow session with the insurer 46 | val session = initiateFlow(insurer) 47 | 48 | // send the claim request and receive the claim state 49 | val insurerQuote = session.sendAndReceive>(treatmentCoverageEstimation).unwrap { it } 50 | 51 | println("Received quote: ${insurerQuote} from insurer ${insurer}") 52 | Pair(insurerQuote, session) 53 | }.sortedByDescending { it.first } 54 | 55 | for ((_, session) in quotes.drop(1)) { 56 | session.send(QuoteStatus.REJECTED_QUOTE) 57 | } 58 | val bestQuote = quotes.first() 59 | bestQuote.second.send(QuoteStatus.ACCEPTED_QUOTE) 60 | return createTransactionSignAndCommit(bestQuote.first, bestQuote.second, issueTreatmentTx) 61 | } 62 | 63 | @Suspendable 64 | private fun createTransactionSignAndCommit(amount: Amount, session: FlowSession, treatmentTx: SignedTransaction): SignedTransaction { 65 | val insurer = session.counterparty 66 | val notary = serviceHub.networkMapCache.notaryIdentities.first() 67 | val treatment = treatmentTx.coreTransaction.outRef(0) 68 | val txb = TransactionBuilder(notary).apply { 69 | addCommand(Command(TreatmentCommand.QuoteTreatment(), listOf(insurer.owningKey, ourIdentity.owningKey))) 70 | addInputState(treatment) 71 | addOutputState(treatment.state.copy(data = treatment.state.data.let { 72 | TreatmentState( 73 | treatment = it.treatment, 74 | estimatedTreatmentCost = treatmentCoverageEstimation.estimatedAmount, 75 | treatmentCost = null, 76 | amountPayed = null, 77 | insurerQuote = InsurerQuote(insurer, amount), 78 | treatmentStatus = TreatmentStatus.QUOTED, 79 | linearId = it.linearId 80 | ) 81 | })) 82 | } 83 | val stx = serviceHub.signInitialTransaction(txb) // hospital signs the transaction 84 | val fullySignedTransaction = subFlow(CollectSignaturesFlow(stx, listOf(session))) 85 | return subFlow(FinalityFlow(fullySignedTransaction)) 86 | } 87 | 88 | @Suspendable 89 | private fun estimateTreatmentState(): SignedTransaction { 90 | val notary = serviceHub.networkMapCache.notaryIdentities.first() 91 | val txb = TransactionBuilder(notary).apply { 92 | addCommand(Command(TreatmentCommand.EstimateTreatment(), listOf(ourIdentity.owningKey))) 93 | addOutputState( 94 | TreatmentState( 95 | treatment = treatmentCoverageEstimation.treatment, 96 | estimatedTreatmentCost = treatmentCoverageEstimation.estimatedAmount, 97 | treatmentCost = null, 98 | amountPayed = null, 99 | insurerQuote = null, 100 | treatmentStatus = TreatmentStatus.ESTIMATED), 101 | TreatmentContract.CONTRACT_ID, notary) 102 | } 103 | val stx = serviceHub.signInitialTransaction(txb) 104 | 105 | return subFlow(FinalityFlow(stx)) 106 | } 107 | } 108 | 109 | @CordaSerializable 110 | sealed class QuoteStatus { 111 | @CordaSerializable 112 | object REJECTED_QUOTE : QuoteStatus() 113 | 114 | @CordaSerializable 115 | object ACCEPTED_QUOTE : QuoteStatus() 116 | } 117 | 118 | /** 119 | * This class handles the insurers side of the flow and initiated by [RetrieveInsurerQuotesFlow] 120 | */ 121 | @InitiatedBy(RetrieveInsurerQuotesFlow::class) 122 | class InsurerRespondFlow(private val session: FlowSession) : FlowLogic() { 123 | 124 | @Suspendable 125 | override fun call(): SignedTransaction? { 126 | val treatment = session.receive().unwrap { treatmentCost -> 127 | requireThat { 128 | //todo - check that the patient is insured by us 129 | } 130 | treatmentCost // return the claim because we've passed our checks for the payload 131 | } 132 | 133 | val quotedAmount = calculateAmountWeCanPay(treatment) 134 | val status = session.sendAndReceive(quotedAmount).unwrap { it } 135 | 136 | if (status == QuoteStatus.ACCEPTED_QUOTE) { 137 | val signTransactionFlow = object : SignTransactionFlow(session, SignTransactionFlow.tracker()) { 138 | override fun checkTransaction(stx: SignedTransaction) = requireThat { 139 | val tx = stx.coreTransaction.outputsOfType().single() 140 | stx.verify(serviceHub, checkSufficientSignatures = false) 141 | "We sign for the treatment that we were required to quote." using (tx.treatment == treatment.treatment) 142 | "We sign the amount we quoted" using (tx.insurerQuote == InsurerQuote(ourIdentity, quotedAmount)) 143 | } 144 | } 145 | // we invoke the sign Transaction flow which in turn awaits the CollectSignaturesFlow above 146 | return subFlow(signTransactionFlow) 147 | } 148 | return null 149 | } 150 | 151 | // This performs a highly complex algorithm. 152 | @Suspendable 153 | private fun calculateAmountWeCanPay(treatmentCoverageEstimation: TreatmentCoverageEstimation): Amount { 154 | val percentage = (Random().nextDouble() * 100).toInt() 155 | val amount = treatmentCoverageEstimation.estimatedAmount 156 | return amount.copy(quantity = (amount.quantity * percentage) / 100) 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /cordapp/src/main/kotlin/net/cordaclub/marge/TriggerTreatmentPaymentsFlow.kt: -------------------------------------------------------------------------------- 1 | package net.cordaclub.marge 2 | 3 | import co.paralleluniverse.fibers.Suspendable 4 | import io.cordite.dgl.corda.account.GetAccountFlow 5 | import net.corda.core.contracts.Amount 6 | import net.corda.core.contracts.Command 7 | import net.corda.core.contracts.StateAndRef 8 | import net.corda.core.flows.FinalityFlow 9 | import net.corda.core.flows.FlowLogic 10 | import net.corda.core.flows.StartableByRPC 11 | import net.corda.core.flows.StartableByService 12 | import net.corda.core.transactions.SignedTransaction 13 | import net.corda.core.transactions.TransactionBuilder 14 | import net.corda.core.utilities.seconds 15 | import net.cordaclub.marge.hospital.HospitalAPI 16 | import java.util.* 17 | 18 | /** 19 | * Second Flow! 20 | * This is run by the Hospital when the treatment is over. 21 | * 22 | * 1) It will transition the TreatmentState to the Finalized status. 23 | * 2) It will requests payments from the insurer and patient. 24 | */ 25 | @StartableByRPC 26 | @StartableByService 27 | class TriggerTreatmentPaymentsFlow(private val treatmentState: StateAndRef, private val realTreatmentCost: Amount) : FlowLogic() { 28 | 29 | @Suspendable 30 | override fun call() { 31 | 32 | val finalisedTreatment = finaliseTreatment() 33 | sleep(10.seconds) 34 | 35 | val hospitalAccount = subFlow(GetAccountFlow(HospitalAPI.HOSPITAL_ACCOUNT)).state.data.address 36 | 37 | val paymentFromInsurerTx = subFlow(InsurerFlows.InsurerTreatmentPaymentFlow(finalisedTreatment.coreTransaction.outRef(0), hospitalAccount)) 38 | 39 | sleep(10.seconds) 40 | 41 | // the patient needs to cover the difference 42 | subFlow(PatientFlows.PatientTreatmentPaymentFlow(paymentFromInsurerTx, hospitalAccount)) 43 | 44 | println("Finished TriggerTreatmentPaymentsFlow") 45 | } 46 | 47 | @Suspendable 48 | private fun finaliseTreatment(): SignedTransaction { 49 | val notary = serviceHub.networkMapCache.notaryIdentities.first() 50 | val txb = TransactionBuilder(notary).apply { 51 | addCommand(Command(TreatmentCommand.FinaliseTreatment(), listOf(ourIdentity.owningKey))) 52 | addInputState(treatmentState) 53 | addOutputState(treatmentState.state.copy(data = treatmentState.state.data.let { 54 | TreatmentState( 55 | treatment = it.treatment, 56 | estimatedTreatmentCost = it.estimatedTreatmentCost, 57 | treatmentCost = realTreatmentCost, 58 | amountPayed = null, 59 | insurerQuote = it.insurerQuote, 60 | treatmentStatus = TreatmentStatus.FINALISED, 61 | linearId = it.linearId 62 | ) 63 | })) 64 | } 65 | val stx = serviceHub.signInitialTransaction(txb) 66 | return subFlow(FinalityFlow(stx)) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /cordapp/src/main/kotlin/net/cordaclub/marge/bank/BankAPI.kt: -------------------------------------------------------------------------------- 1 | package net.cordaclub.marge.bank 2 | 3 | import io.cordite.dgl.corda.account.Account 4 | import io.cordite.dgl.corda.account.AccountAddress 5 | import io.cordite.dgl.corda.account.CreateAccountFlow 6 | import io.cordite.dgl.corda.impl.LedgerApiImpl 7 | import io.vertx.core.Future 8 | import net.corda.core.node.AppServiceHub 9 | import net.corda.core.utilities.loggerFor 10 | import net.cordaclub.marge.Initializer 11 | import net.cordaclub.marge.Insurers 12 | import net.cordaclub.marge.Patient 13 | import net.cordaclub.marge.hospital.HospitalAPI 14 | import net.cordaclub.marge.insurer.InsurerAPI 15 | import net.cordaclub.marge.util.onFail 16 | import net.cordaclub.marge.util.onSuccess 17 | import net.cordaclub.marge.util.toEasyFuture 18 | 19 | class BankAPI(private val serviceHub: AppServiceHub, private val patients: List, private val ledger: LedgerApiImpl) : Initializer() { 20 | companion object { 21 | const val BANK_ISSUANCE_ACCOUNT = "bank" 22 | const val TOKEN_SYMBOL = "GBP" 23 | var TOKEN_TYPE_URI: String = "" 24 | 25 | private val log = loggerFor() 26 | } 27 | 28 | override fun initialiseDemo(): Future { 29 | return if (!initialised) { 30 | initialised = true 31 | val notary = serviceHub.networkMapCache.notaryIdentities.first() 32 | 33 | serviceHub.networkMapCache.allNodes.flatMap { it.legalIdentities }.map { it.name } 34 | .filter { it.organisation.contains("insurer", true) } 35 | 36 | val hospitals = serviceHub.networkMapCache.allNodes.flatMap { it.legalIdentities }.map { it.name } 37 | .filter { it.organisation.contains("hospital", true) } 38 | 39 | val totalOtherPartyTransfers = Insurers.allInsurers.size * 1_000_000 + hospitals.size * 500_000 40 | ledger.createTokenType(TOKEN_SYMBOL, 2, notary.name) 41 | .onSuccess { TOKEN_TYPE_URI = it.descriptor.uri } 42 | .compose { ledger.createAccount(BANK_ISSUANCE_ACCOUNT, notary.name) } 43 | .compose { ledger.issueToken( 44 | BANK_ISSUANCE_ACCOUNT, 45 | "$totalOtherPartyTransfers.00", 46 | TOKEN_SYMBOL, 47 | "initial issuance", 48 | notary.name 49 | ) } 50 | .compose { 51 | // create the accounts for the patients 52 | val requests = patients.map { CreateAccountFlow.Request(it.name) } 53 | serviceHub.startFlow(CreateAccountFlow(requests, notary)).toEasyFuture() 54 | } 55 | .compose { 56 | // sequentially issue to each patient 57 | // we do this because it's easier to fold 58 | patients.fold(Future.succeededFuture()) { acc, patient -> 59 | acc.compose { ledger.issueToken( 60 | patient.name, 61 | "100000.00", 62 | TOKEN_SYMBOL, 63 | "pocket money", 64 | notary.name 65 | ).mapEmpty() } 66 | } 67 | } 68 | .compose { 69 | // transfer funds to the insurers 70 | Insurers.allInsurers.fold(Future.succeededFuture()) { acc, insurer -> 71 | val insurerAccount = AccountAddress(InsurerAPI.INSURER_ACCOUNT, insurer).toString() 72 | acc.compose { 73 | ledger.transferToken( 74 | "1000000.00", 75 | TOKEN_TYPE_URI, 76 | BANK_ISSUANCE_ACCOUNT, 77 | insurerAccount, 78 | "there you go", 79 | notary.name 80 | ).mapEmpty() 81 | } 82 | } 83 | } 84 | .compose { 85 | // transfer funds to the insurers 86 | hospitals.fold(Future.succeededFuture()) { acc, hospital -> 87 | val hospitalAccount = AccountAddress(HospitalAPI.HOSPITAL_ACCOUNT, hospital).toString() 88 | // acc.compose { 89 | // ledger.transferToken( 90 | // "0.00", 91 | // TOKEN_TYPE_URI, 92 | // BANK_ISSUANCE_ACCOUNT, 93 | // hospitalAccount, 94 | // "there you go", 95 | // notary.name 96 | // ).mapEmpty() 97 | // } 98 | Future.succeededFuture() 99 | } 100 | } 101 | .onSuccess { 102 | log.info("bank initialised") 103 | }.onFail { 104 | log.error("failed to initialise bank!", it) 105 | } 106 | } else { 107 | Future.succeededFuture() 108 | } 109 | } 110 | 111 | fun getInitialState() : Future { 112 | return initialiseDemo() 113 | .compose { ledger.listAccounts() } 114 | .map { accounts -> 115 | 116 | val balances = accounts.map { it.address.accountId } 117 | .sorted() 118 | .map { 119 | val balance = Account.getBalances(serviceHub, it).firstOrNull() 120 | ?.toDecimal()?.toString()?:"0.00" 121 | it to balance}.toMap() 122 | BankInitialState(serviceHub.myInfo.legalIdentities.first().name.organisation, balances) 123 | } 124 | } 125 | } 126 | 127 | data class BankInitialState(val name: String, val balances: Map) 128 | -------------------------------------------------------------------------------- /cordapp/src/main/kotlin/net/cordaclub/marge/hospital/HospitalAPI.kt: -------------------------------------------------------------------------------- 1 | package net.cordaclub.marge.hospital 2 | 3 | import io.bluebank.braid.corda.services.transaction 4 | import io.cordite.dgl.corda.impl.LedgerApiImpl 5 | import io.cordite.dgl.corda.token.listAllTokenTypes 6 | import io.vertx.core.Future 7 | import net.corda.core.contracts.Amount 8 | import net.corda.core.contracts.UniqueIdentifier 9 | import net.corda.core.node.AppServiceHub 10 | import net.corda.core.node.services.queryBy 11 | import net.corda.core.node.services.vault.QueryCriteria 12 | import net.corda.core.utilities.loggerFor 13 | import net.corda.finance.GBP 14 | import net.cordaclub.marge.* 15 | import net.cordaclub.marge.util.* 16 | import rx.Observable 17 | import java.math.BigDecimal 18 | import java.util.* 19 | 20 | class HospitalAPI(private val serviceHub: AppServiceHub) : Initializer(){ 21 | companion object { 22 | const val HOSPITAL_ACCOUNT = "hospital" 23 | private val log = loggerFor() 24 | } 25 | 26 | val ledgerApi = LedgerApiImpl(serviceHub) 27 | 28 | override fun initialiseDemo() : Future { 29 | return if (!initialised) { 30 | initialised = true 31 | val notary = serviceHub.networkMapCache.notaryIdentities.first() 32 | ledgerApi.createAccount(HOSPITAL_ACCOUNT, notary.name).mapEmpty() 33 | .recover { 34 | log.info("account exists") 35 | Future.succeededFuture() 36 | } 37 | .onSuccess { log.info("hospital initialised") } 38 | .onFail { log.info("failed to initialise hospital") } 39 | } else { 40 | Future.succeededFuture() 41 | } 42 | } 43 | 44 | fun getInitialState(): Future { 45 | return initialiseDemo() 46 | .compose { 47 | ledgerApi.balanceForAccount(HOSPITAL_ACCOUNT) 48 | } 49 | .map { balances -> 50 | val balancesString = balances.firstOrNull()?.toDecimal()?.toString()?:"0.00" 51 | HospitalInitialState( 52 | serviceHub.myInfo.legalIdentities.first().name.organisation, 53 | Patients.allPatients, 54 | balancesString, 55 | serviceHub.loadTreatments().map { it.linearId.id.toString() to it }.toMap() 56 | ) 57 | } 58 | } 59 | 60 | fun processTreatmentRequest(request: TreatmentRequest) : Future { 61 | val flow = try { 62 | val patient = Patients.allPatients.firstOrNull { it.name == request.name } 63 | ?: throw RuntimeException("could not find patient ${request.name}") 64 | 65 | val amount = BigDecimal(request.amount).longValueExact() * 100 66 | val estimation = TreatmentCoverageEstimation( 67 | Treatment( 68 | patient, 69 | request.description, 70 | this.serviceHub.myInfo.legalIdentities.first() 71 | ), Amount(amount, GBP)) 72 | 73 | val insurers = Insurers.allInsurers.map { 74 | serviceHub.networkMapCache.getNodeByLegalName(it)?.legalIdentities?.first() ?: throw RuntimeException("failed to locate insurer $it") 75 | } 76 | InsurerQuotingFlows.RetrieveInsurerQuotesFlow(estimation, insurers) 77 | } catch (err: Throwable) { 78 | return Future.failedFuture(err) 79 | } 80 | return serviceHub.startFlow(flow).toEasyFuture() 81 | .map { it.coreTransaction.outputsOfType(TreatmentState::class.java).first() } 82 | } 83 | 84 | fun listenForTreatments() : Observable> { 85 | return serviceHub.listenForTreatments() 86 | } 87 | 88 | fun requestPayment(id: String, amount: Long) : Future { 89 | val criteria = QueryCriteria.LinearStateQueryCriteria(linearId = listOf(UniqueIdentifier(id = UUID.fromString(id)))) 90 | val treatmentStateAndRef = serviceHub.transaction { 91 | val results = serviceHub.vaultService.queryBy(criteria) 92 | results.states.first() 93 | } 94 | val actualAmount = Amount(amount, GBP) 95 | val flow = TriggerTreatmentPaymentsFlow(treatmentStateAndRef, actualAmount) 96 | return serviceHub.startFlow(flow).toEasyFuture().mapEmpty() 97 | } 98 | } 99 | 100 | data class HospitalInitialState(val name: String, val patients: List, val balance: String, val treatments: Map) 101 | data class TreatmentRequest(val name: String, val description: String, val amount: String) -------------------------------------------------------------------------------- /cordapp/src/main/kotlin/net/cordaclub/marge/insurer/InsurerAPI.kt: -------------------------------------------------------------------------------- 1 | package net.cordaclub.marge.insurer 2 | 3 | import io.bluebank.braid.core.async.getOrThrow 4 | import io.cordite.dgl.corda.account.GetAccountFlow 5 | import io.cordite.dgl.corda.impl.LedgerApiImpl 6 | import io.vertx.core.Future 7 | import net.corda.core.node.AppServiceHub 8 | import net.corda.core.utilities.getOrThrow 9 | import net.cordaclub.marge.Initializer 10 | 11 | class InsurerAPI(private val serviceHub: AppServiceHub) : Initializer(){ 12 | companion object { 13 | const val INSURER_ACCOUNT = "insurer" 14 | } 15 | 16 | override fun initialiseDemo() : Future { 17 | if (!initialised) { 18 | val notary = serviceHub.networkMapCache.notaryIdentities.first() 19 | try { 20 | serviceHub.startFlow(GetAccountFlow(accountId = INSURER_ACCOUNT)).returnValue.getOrThrow() 21 | } catch (e: Exception) { 22 | val ledgerApi = LedgerApiImpl(serviceHub) 23 | val account = ledgerApi.createAccount(INSURER_ACCOUNT, notary.name).getOrThrow() 24 | } 25 | } 26 | initialised = true 27 | return Future.succeededFuture() 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /cordapp/src/main/kotlin/net/cordaclub/marge/util/Futures.kt: -------------------------------------------------------------------------------- 1 | package net.cordaclub.marge.util 2 | 3 | import io.vertx.core.Future 4 | import net.corda.core.concurrent.CordaFuture 5 | import net.corda.core.messaging.FlowHandle 6 | import net.corda.core.utilities.getOrThrow 7 | 8 | fun CordaFuture.toEasyFuture() : Future { 9 | val result = Future.future() 10 | this.then { 11 | try { 12 | result.complete(this.getOrThrow()) 13 | } catch (err: Throwable) { 14 | result.fail(err) 15 | } 16 | } 17 | return result 18 | } 19 | 20 | fun FlowHandle.toEasyFuture() : Future = this.returnValue.toEasyFuture() 21 | 22 | fun Future.onSuccess(fn: (V) -> Unit) : Future { 23 | val result = Future.future() 24 | this.setHandler { 25 | if (succeeded()) { 26 | try { 27 | fn(result()) 28 | result.complete(result()) 29 | } catch (err: Throwable) { 30 | result.fail(err) 31 | } 32 | } else { 33 | result.fail(cause()) 34 | } 35 | } 36 | return result 37 | } 38 | 39 | fun Future.onFail(fn: (Throwable) -> Unit) : Future { 40 | val result = Future.future() 41 | this.setHandler { 42 | if (failed()) { 43 | try { 44 | fn(cause()) 45 | result.fail(cause()) 46 | } catch (err: Throwable) { 47 | result.fail(err) 48 | } 49 | } else { 50 | result.complete(result()) 51 | } 52 | } 53 | return result 54 | } -------------------------------------------------------------------------------- /cordapp/src/main/kotlin/net/cordaclub/marge/util/Queries.kt: -------------------------------------------------------------------------------- 1 | package net.cordaclub.marge.util 2 | 3 | import io.cordite.commons.utils.transaction 4 | import net.corda.core.node.ServiceHub 5 | import net.cordaclub.marge.TreatmentState 6 | import rx.Observable 7 | 8 | fun ServiceHub.listenForTreatments() : Observable> { 9 | return this.transaction { 10 | this.vaultService.trackBy(TreatmentState::class.java).updates.map { it.produced.map { it.state.data } } 11 | } 12 | } 13 | 14 | fun ServiceHub.loadTreatments() : List { 15 | return this.transaction { 16 | this.vaultService.queryBy(TreatmentState::class.java).states.map { it.state.data } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /cordapp/src/main/resources/META-INF/services/net.corda.core.serialization.SerializationWhitelist: -------------------------------------------------------------------------------- 1 | # Register here any serialization whitelists for 3rd party classes extending from net.corda.core.serialization.SerializationWhitelist 2 | net.cordaclub.marge.TemplateSerializationWhitelist 3 | -------------------------------------------------------------------------------- /cordapp/src/main/resources/META-INF/services/net.corda.webserver.services.WebServerPluginRegistry: -------------------------------------------------------------------------------- 1 | # Register here any CorDapp plugins extending from net.corda.webserver.services.WebServerPluginRegistry. 2 | net.cordaclub.marge.TemplateWebPlugin -------------------------------------------------------------------------------- /cordapp/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | [%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n 8 | > 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /cordapp/src/main/resources/templateWeb/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Template CorDapp 7 | 8 | 9 |
Template CorDapp frontend.
10 | 11 | -------------------------------------------------------------------------------- /cordapp/src/main/resources/web/.gitignore: -------------------------------------------------------------------------------- 1 | **/*.hot-update.* -------------------------------------------------------------------------------- /cordapp/src/main/resources/web/bank/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corda-codeclub/marge/c36ba0ba208aad3b015a73bcbcefc4540f031a0a/cordapp/src/main/resources/web/bank/favicon.ico -------------------------------------------------------------------------------- /cordapp/src/main/resources/web/bank/img/logo.82b9c7a5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corda-codeclub/marge/c36ba0ba208aad3b015a73bcbcefc4540f031a0a/cordapp/src/main/resources/web/bank/img/logo.82b9c7a5.png -------------------------------------------------------------------------------- /cordapp/src/main/resources/web/bank/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | bank 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /cordapp/src/main/resources/web/hospital/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corda-codeclub/marge/c36ba0ba208aad3b015a73bcbcefc4540f031a0a/cordapp/src/main/resources/web/hospital/favicon.ico -------------------------------------------------------------------------------- /cordapp/src/main/resources/web/hospital/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Hospital 10 | 11 | 12 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /cordapp/src/main/resources/web/insurer/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corda-codeclub/marge/c36ba0ba208aad3b015a73bcbcefc4540f031a0a/cordapp/src/main/resources/web/insurer/favicon.ico -------------------------------------------------------------------------------- /cordapp/src/main/resources/web/insurer/img/logo.82b9c7a5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corda-codeclub/marge/c36ba0ba208aad3b015a73bcbcefc4540f031a0a/cordapp/src/main/resources/web/insurer/img/logo.82b9c7a5.png -------------------------------------------------------------------------------- /cordapp/src/main/resources/web/insurer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | insurer 9 | 10 | 11 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /cordapp/src/main/web/README.md: -------------------------------------------------------------------------------- 1 | # Web packages 2 | 3 | ## How to build 4 | 5 | `./build.sh` 6 | 7 | This will place the minimised distribution of each website in the respective 8 | `src/main/resources/web/` direcotry. 9 | 10 | ## How to live code a website 11 | 12 | In the IntelliJ runner, setup the `Working Directory` to `/cordapp/src/main/resources`. 13 | In a shell, `cd web/` and run `npm run watch`. This will setup an automatic watch on the site's files and will rebuild on change. 14 | Make sure you keep an eye on its output .. it's running ESLint! 15 | 16 | 17 | -------------------------------------------------------------------------------- /cordapp/src/main/web/bank/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'no-console': 'off', 4 | }, 5 | }; -------------------------------------------------------------------------------- /cordapp/src/main/web/bank/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /cordapp/src/main/web/bank/README.md: -------------------------------------------------------------------------------- 1 | # bank 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | -------------------------------------------------------------------------------- /cordapp/src/main/web/bank/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } -------------------------------------------------------------------------------- /cordapp/src/main/web/bank/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bank", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "watch": "vue-cli-service build --watch", 9 | "lint": "vue-cli-service lint", 10 | "clean": "rimraf ../../resources/web/bank" 11 | }, 12 | "dependencies": { 13 | "braid-client": "^3.2.1-20180803122404565", 14 | "vue": "^2.5.16" 15 | }, 16 | "devDependencies": { 17 | "@vue/cli-plugin-babel": "^3.0.0-rc.10", 18 | "@vue/cli-plugin-eslint": "^3.0.0-rc.10", 19 | "@vue/cli-service": "^3.0.0-rc.10", 20 | "rimraf": "^2.6.2", 21 | "vue-template-compiler": "^2.5.16" 22 | }, 23 | "eslintConfig": { 24 | "root": true, 25 | "env": { 26 | "node": true 27 | }, 28 | "extends": [ 29 | "plugin:vue/essential", 30 | "eslint:recommended" 31 | ], 32 | "rules": {}, 33 | "parserOptions": { 34 | "parser": "babel-eslint" 35 | } 36 | }, 37 | "postcss": { 38 | "plugins": { 39 | "autoprefixer": {} 40 | } 41 | }, 42 | "browserslist": [ 43 | "> 1%", 44 | "last 2 versions", 45 | "not ie <= 8" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /cordapp/src/main/web/bank/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corda-codeclub/marge/c36ba0ba208aad3b015a73bcbcefc4540f031a0a/cordapp/src/main/web/bank/public/favicon.ico -------------------------------------------------------------------------------- /cordapp/src/main/web/bank/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | bank 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /cordapp/src/main/web/bank/src/App.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 61 | 62 | 80 | -------------------------------------------------------------------------------- /cordapp/src/main/web/bank/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corda-codeclub/marge/c36ba0ba208aad3b015a73bcbcefc4540f031a0a/cordapp/src/main/web/bank/src/assets/logo.png -------------------------------------------------------------------------------- /cordapp/src/main/web/bank/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | 4 | Vue.config.productionTip = false 5 | 6 | new Vue({ 7 | render: h => h(App) 8 | }).$mount('#app') 9 | -------------------------------------------------------------------------------- /cordapp/src/main/web/bank/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | outputDir: '../../resources/web/bank/' 3 | }; -------------------------------------------------------------------------------- /cordapp/src/main/web/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | pushd bank 4 | npm install 5 | npm run build 6 | popd 7 | 8 | pushd hospital 9 | npm install 10 | npm run build 11 | popd 12 | 13 | pushd insurer 14 | npm install 15 | npm run build 16 | popd 17 | 18 | -------------------------------------------------------------------------------- /cordapp/src/main/web/hospital/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'no-console': 'off', 4 | }, 5 | }; -------------------------------------------------------------------------------- /cordapp/src/main/web/hospital/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /cordapp/src/main/web/hospital/README.md: -------------------------------------------------------------------------------- 1 | # hospital 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | -------------------------------------------------------------------------------- /cordapp/src/main/web/hospital/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } -------------------------------------------------------------------------------- /cordapp/src/main/web/hospital/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hospital", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "watch": "vue-cli-service build --watch", 9 | "lint": "vue-cli-service lint", 10 | "clean": "rimraf ../../resources/web/hospital" 11 | }, 12 | "dependencies": { 13 | "braid-client": "^3.2.1-20180803122404565", 14 | "vue": "^2.5.16", 15 | "vuetify": "^1.1.9" 16 | }, 17 | "devDependencies": { 18 | "@vue/cli-plugin-babel": "^3.0.0-rc.10", 19 | "@vue/cli-plugin-eslint": "^3.0.0-rc.10", 20 | "@vue/cli-service": "^3.0.0-rc.10", 21 | "rimraf": "^2.6.2", 22 | "vue-template-compiler": "^2.5.16" 23 | }, 24 | "eslintConfig": { 25 | "root": true, 26 | "env": { 27 | "node": true 28 | }, 29 | "extends": [ 30 | "plugin:vue/essential", 31 | "eslint:recommended" 32 | ], 33 | "rules": {}, 34 | "parserOptions": { 35 | "parser": "babel-eslint" 36 | } 37 | }, 38 | "postcss": { 39 | "plugins": { 40 | "autoprefixer": {} 41 | } 42 | }, 43 | "browserslist": [ 44 | "> 1%", 45 | "last 2 versions", 46 | "not ie <= 8" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /cordapp/src/main/web/hospital/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corda-codeclub/marge/c36ba0ba208aad3b015a73bcbcefc4540f031a0a/cordapp/src/main/web/hospital/public/favicon.ico -------------------------------------------------------------------------------- /cordapp/src/main/web/hospital/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Hospital 10 | 11 | 12 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /cordapp/src/main/web/hospital/src/App.vue: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | 102 | 103 | 240 | 241 | 262 | -------------------------------------------------------------------------------- /cordapp/src/main/web/hospital/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corda-codeclub/marge/c36ba0ba208aad3b015a73bcbcefc4540f031a0a/cordapp/src/main/web/hospital/src/assets/logo.png -------------------------------------------------------------------------------- /cordapp/src/main/web/hospital/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import 'vuetify/dist/vuetify.min.css' 3 | import App from './App.vue' 4 | import Vuetify from 'vuetify' 5 | 6 | Vue.config.productionTip = false; 7 | Vue.use(Vuetify); 8 | 9 | new Vue({ 10 | render: h => h(App) 11 | }).$mount('#app'); 12 | -------------------------------------------------------------------------------- /cordapp/src/main/web/hospital/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | outputDir: '../../resources/web/hospital/' 3 | }; -------------------------------------------------------------------------------- /cordapp/src/main/web/insurer/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 -------------------------------------------------------------------------------- /cordapp/src/main/web/insurer/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'no-console': 'off', 4 | }, 5 | }; -------------------------------------------------------------------------------- /cordapp/src/main/web/insurer/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /cordapp/src/main/web/insurer/.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } -------------------------------------------------------------------------------- /cordapp/src/main/web/insurer/README.md: -------------------------------------------------------------------------------- 1 | # insurer 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | -------------------------------------------------------------------------------- /cordapp/src/main/web/insurer/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } -------------------------------------------------------------------------------- /cordapp/src/main/web/insurer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "insurer", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "watch": "vue-cli-service build --watch", 9 | "lint": "vue-cli-service lint", 10 | "clean": "rimraf ../../resources/web/insurer" 11 | }, 12 | "dependencies": { 13 | "braid-client": "^3.2.1-20180803122404565", 14 | "vue": "^2.5.16" 15 | }, 16 | "devDependencies": { 17 | "@vue/cli-plugin-babel": "^3.0.0-rc.10", 18 | "@vue/cli-plugin-eslint": "^3.0.0-rc.10", 19 | "@vue/cli-service": "^3.0.0-rc.10", 20 | "rimraf": "^2.6.2", 21 | "vue-template-compiler": "^2.5.16" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /cordapp/src/main/web/insurer/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corda-codeclub/marge/c36ba0ba208aad3b015a73bcbcefc4540f031a0a/cordapp/src/main/web/insurer/public/favicon.ico -------------------------------------------------------------------------------- /cordapp/src/main/web/insurer/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | insurer 9 | 10 | 11 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /cordapp/src/main/web/insurer/src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 71 | 72 | 86 | -------------------------------------------------------------------------------- /cordapp/src/main/web/insurer/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corda-codeclub/marge/c36ba0ba208aad3b015a73bcbcefc4540f031a0a/cordapp/src/main/web/insurer/src/assets/logo.png -------------------------------------------------------------------------------- /cordapp/src/main/web/insurer/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | 4 | Vue.config.productionTip = false 5 | 6 | new Vue({ 7 | render: h => h(App) 8 | }).$mount('#app') 9 | -------------------------------------------------------------------------------- /cordapp/src/main/web/insurer/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | outputDir: '../../resources/web/insurer/' 3 | }; -------------------------------------------------------------------------------- /cordapp/src/main/web/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | -------------------------------------------------------------------------------- /cordapp/src/test/kotlin/net/cordaclub/marge/ContractTests.kt: -------------------------------------------------------------------------------- 1 | package net.cordaclub.marge 2 | 3 | import net.corda.testing.node.MockServices 4 | import org.junit.Test 5 | 6 | class ContractTests { 7 | private val ledgerServices = MockServices() 8 | 9 | @Test 10 | fun `dummy test`() { 11 | 12 | } 13 | } -------------------------------------------------------------------------------- /cordapp/src/test/kotlin/net/cordaclub/marge/FlowTests.kt: -------------------------------------------------------------------------------- 1 | package net.cordaclub.marge 2 | 3 | import net.corda.testing.node.MockNetwork 4 | import org.junit.After 5 | import org.junit.Before 6 | import org.junit.Test 7 | 8 | class FlowTests { 9 | 10 | private val network = MockNetwork(listOf("net.cordaclub.marge")) 11 | private val hospitalNode = network.createNode() 12 | private val insurerNode = network.createNode() 13 | 14 | init { 15 | listOf(insurerNode).forEach { 16 | it.registerInitiatedFlow(InsurerQuotingFlows.InsurerRespondFlow::class.java) 17 | } 18 | } 19 | 20 | @Before 21 | fun setup() = network.runNetwork() 22 | 23 | @After 24 | fun tearDown() = network.stopNodes() 25 | 26 | @Test 27 | fun `dummy test`() { 28 | // val patient = Patient("fuzz", "NW533428A") 29 | // val claimRequest = TreatmentCoverageEstimation( 30 | // Amount(100, Currency.getInstance("GBP")), 31 | // patient, 32 | // "brain transplant") 33 | // 34 | // val future = hospitalNode.startFlow(RetrieveInsurerQuotesFlow(claimRequest)) 35 | // network.runNetwork() 36 | // val result = future.getOrThrow() 37 | // println(result) 38 | } 39 | } -------------------------------------------------------------------------------- /cordapp/src/test/kotlin/net/cordaclub/marge/NodeDriver.kt: -------------------------------------------------------------------------------- 1 | package net.cordaclub.marge 2 | 3 | import io.vertx.core.Future 4 | import net.corda.core.identity.CordaX500Name 5 | import net.corda.core.messaging.startFlow 6 | import net.corda.core.utilities.getOrThrow 7 | import net.corda.finance.POUNDS 8 | import net.corda.testing.driver.DriverParameters 9 | import net.corda.testing.driver.NodeHandle 10 | import net.corda.testing.driver.driver 11 | import net.corda.testing.driver.internal.InProcessImpl 12 | import net.corda.testing.node.User 13 | import net.cordaclub.marge.InsurerQuotingFlows.RetrieveInsurerQuotesFlow 14 | import net.cordaclub.marge.Insurers.allInsurers 15 | import net.cordaclub.marge.util.onFail 16 | import net.cordaclub.marge.util.onSuccess 17 | import org.slf4j.LoggerFactory 18 | 19 | fun main(args: Array) { 20 | val log = LoggerFactory.getLogger("NodeDriver") 21 | 22 | // No permissions required as we are not invoking flows. 23 | val users = listOf(User("user1", "test", permissions = setOf("ALL"))) 24 | 25 | // the ordering here is important - we want all the respective nodes to setup their accounts before we start issuing currency 26 | val names = listOf(CordaX500Name("Fixalot Hospital", "London", "GB")) + allInsurers + listOf(CordaX500Name("Kaching! Bank", "Paris", "FR")) 27 | 28 | driver(DriverParameters(isDebug = true, waitForAllNodesToFinish = true, startNodesInProcess = true, extraCordappPackagesToScan = listOf("io.cordite"))) { 29 | 30 | val nodes = names.map { 31 | startNode(providedName = it, rpcUsers = users) 32 | }.map { it.getOrThrow() } 33 | 34 | val hospital = nodes.first() 35 | val insurers = nodes.drop(1).dropLast(1) 36 | 37 | log.info("initialing node state") 38 | 39 | nodes.fold(Future.succeededFuture()) { acc, node -> 40 | acc.compose { node.getInitializer().initialiseDemo() } 41 | }.onSuccess { 42 | log.info("nodes initialised") 43 | nodes.forEach { it.openWebPage() } 44 | }.onFail { err -> 45 | log.error("failed to initalise nodes", err) 46 | } 47 | // 48 | // val patient = Patients.allPatients.first() 49 | // val treatment = Treatment(patient, "serious disease", hospital.nodeInfo.legalIdentities.first()) 50 | // val estimation = TreatmentCoverageEstimation(treatment, 100.POUNDS) 51 | // 52 | // val estimationTx = hospital.rpc.startFlow(::RetrieveInsurerQuotesFlow, estimation, insurers.map { it.nodeInfo.legalIdentities.first() }).returnValue.getOrThrow() 53 | // val treatmentState = estimationTx.coreTransaction.outRefsOfType()[0] 54 | // val quote = treatmentState.state.data.insurerQuote!! 55 | // 56 | // Thread.sleep(5000) 57 | // println("Successfully got quote: ${quote.maxCoveredValue} from: ${quote.insurer}") 58 | // 59 | // hospital.rpc.vaultTrack(TreatmentState::class.java).updates.subscribe { vaultUpdate -> 60 | // println("Produced: ${vaultUpdate.produced}") 61 | // println("Consumed: ${vaultUpdate.consumed}") 62 | // } 63 | // 64 | // hospital.rpc.startFlow(::TriggerTreatmentPaymentsFlow, treatmentState, 150.POUNDS).returnValue.getOrThrow() 65 | // 66 | // println("Successfully payed for the treatment.") 67 | } 68 | } 69 | 70 | fun NodeHandle.getInitializer() = (this as InProcessImpl).services.cordaService(DemoService::class.java).service 71 | fun NodeHandle.openWebPage() = (this as InProcessImpl).services.cordaService(DemoService::class.java).openWebPage() -------------------------------------------------------------------------------- /cordapp/src/test/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | [%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n 8 | > 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | name=Test 2 | group=com.template 3 | version=0.1 4 | kotlin.incremental=false -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corda-codeclub/marge/c36ba0ba208aad3b015a73bcbcefc4540f031a0a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Aug 25 12:50:39 BST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /lib/README.txt: -------------------------------------------------------------------------------- 1 | The Quasar.jar in this directory is for runtime instrumentation of classes by Quasar. 2 | 3 | When running corda outside of the given gradle building you must add the following flag with the 4 | correct path to your call to Java: 5 | 6 | java -javaagent:path-to-quasar-jar.jar ... 7 | 8 | See the Quasar docs for more information: http://docs.paralleluniverse.co/quasar/ -------------------------------------------------------------------------------- /lib/quasar.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corda-codeclub/marge/c36ba0ba208aad3b015a73bcbcefc4540f031a0a/lib/quasar.jar -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'cordapp' 2 | include 'cordapp-contracts-states' --------------------------------------------------------------------------------