From a4dd402e5f68c576a65a0d8569169f2bc4c355cf Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Wed, 27 Dec 2023 14:47:01 +0800 Subject: [PATCH 01/25] add client to dollartohousetoken --- .gitignore | 1 + Tokens/dollartohousetoken/README.md | 11 +- Tokens/dollartohousetoken/build.gradle | 5 + .../dollartohousetoken/clients/build.gradle | 56 ++++++ .../net/corda/samples/example/Client.java | 49 +++++ .../samples/example/webserver/Controller.java | 171 ++++++++++++++++++ .../example/webserver/NodeRPCConnection.java | 48 +++++ .../samples/example/webserver/Starter.java | 23 +++ .../clients/src/main/resources/static/app.js | 3 + .../src/main/resources/static/index.html | 15 ++ Tokens/dollartohousetoken/settings.gradle | 3 +- 11 files changed, 383 insertions(+), 2 deletions(-) create mode 100644 Tokens/dollartohousetoken/clients/build.gradle create mode 100644 Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/Client.java create mode 100644 Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java create mode 100644 Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/NodeRPCConnection.java create mode 100644 Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Starter.java create mode 100644 Tokens/dollartohousetoken/clients/src/main/resources/static/app.js create mode 100644 Tokens/dollartohousetoken/clients/src/main/resources/static/index.html diff --git a/.gitignore b/.gitignore index d08af3e7..10f7016c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ **/node_modules/ corda-nodeinfo/corda-nodeinfo.iml *~* +*.prefs diff --git a/Tokens/dollartohousetoken/README.md b/Tokens/dollartohousetoken/README.md index 3f34e869..60981c22 100644 --- a/Tokens/dollartohousetoken/README.md +++ b/Tokens/dollartohousetoken/README.md @@ -62,7 +62,8 @@ We can now check the issued house token in PartyB's vault. Since we issued it as Note that HouseState token is an evolvable token which is a `LinearState`, thus we can check PartyB's vault to view the `EvolvableToken` - run vaultQuery contractStateType: HouseState + //run vaultQuery contractStateType: HouseState + run vaultQuery contractStateType: net.corda.samples.dollartohousetoken.states.HouseState Note the linearId of the HouseState token from the previous step, we will need it to perform our DvP opearation. Goto PartyB's shell to initiate the token sale. @@ -74,3 +75,11 @@ We could now verify that the non-fungible token has been transferred to PartyC a run vaultQuery contractStateType: com.r3.corda.lib.tokens.contracts.states.FungibleToken // Run on PartyC's shell run vaultQuery contractStateType: com.r3.corda.lib.tokens.contracts.states.NonFungibleToken + + +Use RPC client +In one terminal: +./gradlew build +In another terminal: +curl -i -X GET http://localhost:50005/me -H 'Content-Type: application/json' +curl -i -X POST 'http://localhost:50005/create-token?amount=100&recipient=PartyC¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' \ No newline at end of file diff --git a/Tokens/dollartohousetoken/build.gradle b/Tokens/dollartohousetoken/build.gradle index 99a572c9..7d30ef63 100644 --- a/Tokens/dollartohousetoken/build.gradle +++ b/Tokens/dollartohousetoken/build.gradle @@ -17,6 +17,10 @@ buildscript { tokens_release_group = 'com.r3.corda.lib.tokens' tokens_release_version = '1.2' + //springboot + spring_boot_version = '2.0.2.RELEASE' + spring_boot_gradle_plugin_version = '2.0.2.RELEASE' + } repositories { @@ -30,6 +34,7 @@ buildscript { classpath "net.corda.plugins:cordapp:$corda_gradle_plugins_version" classpath "net.corda.plugins:cordformation:$corda_gradle_plugins_version" classpath "net.corda.plugins:quasar-utils:$corda_gradle_plugins_version" + classpath "org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_gradle_plugin_version" } } diff --git a/Tokens/dollartohousetoken/clients/build.gradle b/Tokens/dollartohousetoken/clients/build.gradle new file mode 100644 index 00000000..bff3b3ff --- /dev/null +++ b/Tokens/dollartohousetoken/clients/build.gradle @@ -0,0 +1,56 @@ +apply plugin: 'org.springframework.boot' + +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") + } + } +} + +dependencies { + // Corda dependencies. + compile ("$corda_release_group:corda-rpc:$corda_release_version") { + exclude group: "ch.qos.logback", module: "logback-classic" + } + compile "net.corda:corda-jackson:$corda_release_version" + + // CorDapp dependencies. + compile project(":contracts") + compile project(":workflows") + compile("org.springframework.boot:spring-boot-starter-websocket:$spring_boot_version") { + exclude group: "org.springframework.boot", module: "spring-boot-starter-logging" + } + compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" + compile "org.apache.logging.log4j:log4j-web:${log4j_version}" + compile "org.slf4j:jul-to-slf4j:$slf4j_version" +} + +springBoot { + mainClassName = "net.corda.samples.example.webserver.Starter" +} + +/* The Client is the communication channel between the external and the node. This task will help you immediately + * execute your rpc methods in the main method of the client.kt. You can somewhat see this as a quick test of making + * RPC calls to your nodes. + */ +task runTestClient(type: JavaExec, dependsOn: assemble) { + classpath = sourceSets.main.runtimeClasspath + main = 'net.corda.samples.example.webserver.Client' + args 'localhost:10006', 'user1', 'test' +} + +/* This task will start the springboot server that connects to your node (via RPC connection). All of the http requests + * are in the Controller file. You can leave the Server.kt and NodeRPCConnection.kt file untouched for your use. + */ +task runPartyAServer(type: JavaExec, dependsOn: assemble) { + classpath = sourceSets.main.runtimeClasspath + main = 'net.corda.samples.example.webserver.Starter' + args '--server.port=50005', '--config.rpc.host=localhost', '--config.rpc.port=10006', '--config.rpc.username=user1', '--config.rpc.password=test' +} + +task runPartyBServer(type: JavaExec, dependsOn: assemble) { + classpath = sourceSets.main.runtimeClasspath + main = 'net.corda.samples.example.webserver.Starter' + args '--server.port=50006', '--config.rpc.host=localhost', '--config.rpc.port=10009', '--config.rpc.username=user1', '--config.rpc.password=test' +} diff --git a/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/Client.java b/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/Client.java new file mode 100644 index 00000000..62ac60fe --- /dev/null +++ b/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/Client.java @@ -0,0 +1,49 @@ +package net.corda.samples.example; + +import net.corda.client.rpc.CordaRPCClient; +import net.corda.client.rpc.CordaRPCConnection; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.node.NodeInfo; +import net.corda.core.utilities.NetworkHostAndPort; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import static net.corda.core.utilities.NetworkHostAndPort.parse; + +/** + * Connects to a Corda node via RPC and performs RPC operations on the node. + * + * The RPC connection is configured using command line arguments. + */ +public class Client { + private static final Logger logger = LoggerFactory.getLogger(Client.class); + + public static void main(String[] args) { + // Create an RPC connection to the node. + if (args.length != 3) throw new IllegalArgumentException("Usage: Client "); + final NetworkHostAndPort nodeAddress = parse(args[0]); + final String rpcUsername = args[1]; + final String rpcPassword = args[2]; + final CordaRPCClient client = new CordaRPCClient(nodeAddress); + final CordaRPCConnection clientConnection = client.start(rpcUsername, rpcPassword); + final CordaRPCOps proxy = clientConnection.getProxy(); + + // Interact with the node. + // Example #1, here we print the nodes on the network. + final List nodes = proxy.networkMapSnapshot(); + System.out.println("\n-- Here is the networkMap snapshot --"); + logger.info("{}", nodes); + + // Example #2, here we print the PartyA's node info + CordaX500Name name = proxy.nodeInfo().getLegalIdentities().get(0).getName();//nodeInfo().legalIdentities.first().name + System.out.println("\n-- Here is the node info of the node that the client connected to --"); + logger.info("{}", name); + + //Close the client connection + clientConnection.close(); + + } +} \ No newline at end of file diff --git a/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java b/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java new file mode 100644 index 00000000..b951b95d --- /dev/null +++ b/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java @@ -0,0 +1,171 @@ +package net.corda.samples.example.webserver; + +import com.fasterxml.jackson.databind.ObjectMapper; +import net.corda.client.jackson.JacksonSupport; +import net.corda.core.contracts.*; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.node.NodeInfo; +import net.corda.core.transactions.SignedTransaction; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.*; + +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import net.corda.samples.dollartohousetoken.flows.FiatCurrencyIssueFlow; + +/** + * Define your API endpoints here. + */ +@RestController +@RequestMapping("/") // The paths for HTTP requests are relative to this base path. +public class Controller { + private static final Logger logger = LoggerFactory.getLogger(RestController.class); + private final CordaRPCOps proxy; + private final CordaX500Name me; + + public Controller(NodeRPCConnection rpc) { + this.proxy = rpc.proxy; + this.me = proxy.nodeInfo().getLegalIdentities().get(0).getName(); + + } + + /** Helpers for filtering the network map cache. */ + public String toDisplayString(X500Name name){ + return BCStyle.INSTANCE.toString(name); + } + + private boolean isNotary(NodeInfo nodeInfo) { + return !proxy.notaryIdentities() + .stream().filter(el -> nodeInfo.isLegalIdentity(el)) + .collect(Collectors.toList()).isEmpty(); + } + + private boolean isMe(NodeInfo nodeInfo){ + return nodeInfo.getLegalIdentities().get(0).getName().equals(me); + } + + private boolean isNetworkMap(NodeInfo nodeInfo){ + return nodeInfo.getLegalIdentities().get(0).getName().getOrganisation().equals("Network Map Service"); + } + + @Configuration + class Plugin { + @Bean + public ObjectMapper registerModule() { + return JacksonSupport.createNonRpcMapper(); + } + } + + @GetMapping(value = "/status", produces = TEXT_PLAIN_VALUE) + private String status() { + return "200"; + } + + @GetMapping(value = "/servertime", produces = TEXT_PLAIN_VALUE) + private String serverTime() { + return (LocalDateTime.ofInstant(proxy.currentNodeTime(), ZoneId.of("UTC"))).toString(); + } + + @GetMapping(value = "/addresses", produces = TEXT_PLAIN_VALUE) + private String addresses() { + return proxy.nodeInfo().getAddresses().toString(); + } + + @GetMapping(value = "/identities", produces = TEXT_PLAIN_VALUE) + private String identities() { + return proxy.nodeInfo().getLegalIdentities().toString(); + } + + @GetMapping(value = "/platformversion", produces = TEXT_PLAIN_VALUE) + private String platformVersion() { + return Integer.toString(proxy.nodeInfo().getPlatformVersion()); + } + + @GetMapping(value = "/peers", produces = APPLICATION_JSON_VALUE) + public HashMap> getPeers() { + HashMap> myMap = new HashMap<>(); + + // Find all nodes that are not notaries, ourself, or the network map. + Stream filteredNodes = proxy.networkMapSnapshot().stream() + .filter(el -> !isNotary(el) && !isMe(el) && !isNetworkMap(el)); + // Get their names as strings + List nodeNames = filteredNodes.map(el -> el.getLegalIdentities().get(0).getName().toString()) + .collect(Collectors.toList()); + + myMap.put("peers", nodeNames); + return myMap; + } + + @GetMapping(value = "/notaries", produces = TEXT_PLAIN_VALUE) + private String notaries() { + return proxy.notaryIdentities().toString(); + } + + @GetMapping(value = "/flows", produces = TEXT_PLAIN_VALUE) + private String flows() { + return proxy.registeredFlows().toString(); + } + + @GetMapping(value = "/states", produces = TEXT_PLAIN_VALUE) + private String states() { + return proxy.vaultQuery(ContractState.class).getStates().toString(); + } + + @GetMapping(value = "/me",produces = APPLICATION_JSON_VALUE) + private HashMap whoami(){ + HashMap myMap = new HashMap<>(); + myMap.put("me", me.toString()); + return myMap; + } + + @PostMapping(value = "create-token", produces = TEXT_PLAIN_VALUE, headers = "Content-Type=application/x-www-form-urlencoded") + public ResponseEntity issueIOU(HttpServletRequest request) throws IllegalArgumentException { + + int amount = Integer.valueOf(request.getParameter("amount")); + String party = request.getParameter("recipient"); + String currency = request.getParameter("currency"); + // Get party objects for recipient and the currency. + + //CordaX500Name partyX500Name = CordaX500Name.parse(party); + //Party otherParty = proxy.wellKnownPartyFromX500Name(partyX500Name); + + // Create a new token state using the parameters given. + try { + // Start the IssueFlow. We block and waits for the flow to return. + SignedTransaction result = proxy.startTrackedFlowDynamic(FiatCurrencyIssueFlow.class, currency, amount, party) + .getReturnValue().get(); + // Return the response. + return ResponseEntity + .status(HttpStatus.CREATED) + .body("Transaction id " + result.getId() + " committed to ledger.\n " + + result.getTx().getOutput(0)); + // For the purposes of this demo app, we do not differentiate by exception type. + } catch (Exception e) { + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/NodeRPCConnection.java b/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/NodeRPCConnection.java new file mode 100644 index 00000000..05376c51 --- /dev/null +++ b/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/NodeRPCConnection.java @@ -0,0 +1,48 @@ +package net.corda.samples.example.webserver; + +import net.corda.client.rpc.CordaRPCClient; +import net.corda.client.rpc.CordaRPCConnection; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.utilities.NetworkHostAndPort; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +/** + * Wraps an RPC connection to a Corda node. + * + * The RPC connection is configured using command line arguments. + */ +@Component +public class NodeRPCConnection implements AutoCloseable { + // The host of the node we are connecting to. + @Value("${config.rpc.host}") + private String host; + // The RPC port of the node we are connecting to. + @Value("${config.rpc.username}") + private String username; + // The username for logging into the RPC client. + @Value("${config.rpc.password}") + private String password; + // The password for logging into the RPC client. + @Value("${config.rpc.port}") + private int rpcPort; + + private CordaRPCConnection rpcConnection; + CordaRPCOps proxy; + + @PostConstruct + public void initialiseNodeRPCConnection() { + NetworkHostAndPort rpcAddress = new NetworkHostAndPort(host, rpcPort); + CordaRPCClient rpcClient = new CordaRPCClient(rpcAddress); + rpcConnection = rpcClient.start(username, password); + proxy = rpcConnection.getProxy(); + } + + @PreDestroy + public void close() { + rpcConnection.notifyServerAndClose(); + } +} \ No newline at end of file diff --git a/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Starter.java b/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Starter.java new file mode 100644 index 00000000..d0c37b2b --- /dev/null +++ b/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Starter.java @@ -0,0 +1,23 @@ +package net.corda.samples.example.webserver; + +import org.springframework.boot.Banner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import static org.springframework.boot.WebApplicationType.SERVLET; + +/** + * Our Spring Boot application. + */ +@SpringBootApplication +public class Starter { + /** + * Starts our Spring Boot application. + */ + public static void main(String[] args) { + SpringApplication app = new SpringApplication(Starter.class); + app.setBannerMode(Banner.Mode.OFF); + app.setWebApplicationType(SERVLET); + app.run(args); + } +} \ No newline at end of file diff --git a/Tokens/dollartohousetoken/clients/src/main/resources/static/app.js b/Tokens/dollartohousetoken/clients/src/main/resources/static/app.js new file mode 100644 index 00000000..c58d2de8 --- /dev/null +++ b/Tokens/dollartohousetoken/clients/src/main/resources/static/app.js @@ -0,0 +1,3 @@ +"use strict"; + +// Define your client-side logic here. \ No newline at end of file diff --git a/Tokens/dollartohousetoken/clients/src/main/resources/static/index.html b/Tokens/dollartohousetoken/clients/src/main/resources/static/index.html new file mode 100644 index 00000000..3ab26ca0 --- /dev/null +++ b/Tokens/dollartohousetoken/clients/src/main/resources/static/index.html @@ -0,0 +1,15 @@ + + + + + + Example front-end. + + +
+ Corda +

CorDapp Template (Java Version)

+

Learn more about how to build CorDapps at sample-java

+
+ + \ No newline at end of file diff --git a/Tokens/dollartohousetoken/settings.gradle b/Tokens/dollartohousetoken/settings.gradle index b4446eaf..2514aca2 100644 --- a/Tokens/dollartohousetoken/settings.gradle +++ b/Tokens/dollartohousetoken/settings.gradle @@ -1,2 +1,3 @@ include 'workflows' -include 'contracts' \ No newline at end of file +include 'contracts' +include 'clients' \ No newline at end of file From 1f7f2e2a732ec34370d9957ca4bf5a8661953ebd Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Wed, 27 Dec 2023 15:45:15 +0800 Subject: [PATCH 02/25] whitespace --- Tokens/dollartohousetoken/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Tokens/dollartohousetoken/README.md b/Tokens/dollartohousetoken/README.md index 60981c22..195c1790 100644 --- a/Tokens/dollartohousetoken/README.md +++ b/Tokens/dollartohousetoken/README.md @@ -77,9 +77,9 @@ We could now verify that the non-fungible token has been transferred to PartyC a run vaultQuery contractStateType: com.r3.corda.lib.tokens.contracts.states.NonFungibleToken -Use RPC client -In one terminal: -./gradlew build -In another terminal: -curl -i -X GET http://localhost:50005/me -H 'Content-Type: application/json' -curl -i -X POST 'http://localhost:50005/create-token?amount=100&recipient=PartyC¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' \ No newline at end of file +Use RPC client +In one terminal: +./gradlew build +In another terminal: +curl -i -X GET http://localhost:50005/me -H 'Content-Type: application/json' +curl -i -X POST 'http://localhost:50005/create-token?amount=100&recipient=PartyC¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' \ No newline at end of file From 3ee55b09d5bd88a45b802bd3a49ee1715c0baa56 Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Wed, 27 Dec 2023 20:16:54 +0800 Subject: [PATCH 03/25] fix: A FlowLogicRef cannot be constructed for FlowLogic of type net.corda.samples.dollartohousetoken.flows.FiatCurrencyIssueFlow: due to ambiguous match against the constructors --- .../corda/samples/example/webserver/Controller.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java b/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java index b951b95d..7da08e3b 100644 --- a/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java +++ b/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java @@ -143,18 +143,20 @@ private HashMap whoami(){ @PostMapping(value = "create-token", produces = TEXT_PLAIN_VALUE, headers = "Content-Type=application/x-www-form-urlencoded") public ResponseEntity issueIOU(HttpServletRequest request) throws IllegalArgumentException { - int amount = Integer.valueOf(request.getParameter("amount")); + Long amount = Long.valueOf(request.getParameter("amount")); String party = request.getParameter("recipient"); String currency = request.getParameter("currency"); // Get party objects for recipient and the currency. - - //CordaX500Name partyX500Name = CordaX500Name.parse(party); - //Party otherParty = proxy.wellKnownPartyFromX500Name(partyX500Name); + System.out.println(amount); + System.out.println(currency); + System.out.println(party); + CordaX500Name partyX500Name = CordaX500Name.parse(party); + Party otherParty = proxy.wellKnownPartyFromX500Name(partyX500Name); // Create a new token state using the parameters given. try { // Start the IssueFlow. We block and waits for the flow to return. - SignedTransaction result = proxy.startTrackedFlowDynamic(FiatCurrencyIssueFlow.class, currency, amount, party) + SignedTransaction result = proxy.startTrackedFlowDynamic(FiatCurrencyIssueFlow.class, currency, amount, otherParty) .getReturnValue().get(); // Return the response. return ResponseEntity From a072b5acbb4142f048967e6352de0259ae1945d0 Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Wed, 27 Dec 2023 20:21:04 +0800 Subject: [PATCH 04/25] update with create-token query string --- Tokens/dollartohousetoken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tokens/dollartohousetoken/README.md b/Tokens/dollartohousetoken/README.md index 195c1790..b17f563c 100644 --- a/Tokens/dollartohousetoken/README.md +++ b/Tokens/dollartohousetoken/README.md @@ -82,4 +82,4 @@ In one terminal: ./gradlew build In another terminal: curl -i -X GET http://localhost:50005/me -H 'Content-Type: application/json' -curl -i -X POST 'http://localhost:50005/create-token?amount=100&recipient=PartyC¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' \ No newline at end of file +curl -i -X POST 'http://localhost:50005/create-token?amount=100&recipient=O=PartyC,L=Mumbai,C=IN¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' \ No newline at end of file From efa728e32241b29fe3f25b3461e831a2b0ad088a Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Wed, 27 Dec 2023 20:43:13 +0800 Subject: [PATCH 05/25] update readme with client command --- Tokens/dollartohousetoken/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tokens/dollartohousetoken/README.md b/Tokens/dollartohousetoken/README.md index b17f563c..9ee5b74b 100644 --- a/Tokens/dollartohousetoken/README.md +++ b/Tokens/dollartohousetoken/README.md @@ -79,7 +79,8 @@ We could now verify that the non-fungible token has been transferred to PartyC a Use RPC client In one terminal: -./gradlew build +./gradlew assemble +java -jar clients/build/libs/clients-1.0.jar --server.port=50005 --config.rpc.host=localhost --config.rpc.port=10006 --config.rpc.username=user1 --config.rpc.password=test In another terminal: curl -i -X GET http://localhost:50005/me -H 'Content-Type: application/json' curl -i -X POST 'http://localhost:50005/create-token?amount=100&recipient=O=PartyC,L=Mumbai,C=IN¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' \ No newline at end of file From cdda6e2581b081c2b26e188fc1b077af4c83e008 Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Thu, 4 Jan 2024 13:56:15 +0800 Subject: [PATCH 06/25] add FiatCurrencyMoveFlow --- .../flows/FiatCurrencyMoveFlow.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyMoveFlow.java diff --git a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyMoveFlow.java b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyMoveFlow.java new file mode 100644 index 00000000..924ccf2b --- /dev/null +++ b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyMoveFlow.java @@ -0,0 +1,67 @@ +package net.corda.samples.dollartohousetoken.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.tokens.contracts.states.FungibleToken; +import com.r3.corda.lib.tokens.contracts.types.TokenType; +import com.r3.corda.lib.tokens.money.FiatCurrency; +import com.r3.corda.lib.tokens.selection.database.selector.DatabaseTokenSelection; +import com.r3.corda.lib.tokens.workflows.types.PartyAndAmount; +import kotlin.Pair; +import net.corda.core.contracts.Amount; +import net.corda.core.contracts.StateAndRef; +import net.corda.core.flows.*; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.identity.Party; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.Currency; +import java.util.List; +import java.util.Optional; + +/* + * This flow transfers the equivalent amount of fiat currency to the seller. + */ +@StartableByRPC +public class FiatCurrencyMoveFlow extends FlowLogic { + + private final String currency; + private final Long amount; + private final Party recipient; + + public FiatCurrencyMoveFlow(String currency, Long amount, Party recipient) { + this.currency = currency; + this.amount = amount; + this.recipient = recipient; + } + + @Override + @Suspendable + public SignedTransaction call() throws FlowException { + + Optional optFoo = Optional.ofNullable(null); + long longAmount = optFoo.orElse( this.amount ); + /* Create instance of the fiat currency token amount */ + Amount priceToken = new Amount<>(longAmount, FiatCurrency.Companion.getInstance(this.currency)); + + /* Generate the move proposal, it returns the input-output pair for the fiat currency transfer, which we need to + send to the Initiator */ + Pair>, List> inputsAndOutputs = new DatabaseTokenSelection(getServiceHub()) + // here we are generating input and output states which send the correct amount to the seller, and any change back to buyer + .generateMove(Collections.singletonList(new Pair<>(this.recipient, priceToken)), getOurIdentity()); + + FlowSession counterpartySession = initiateFlow(this.recipient); + /* Call SendStateAndRefFlow to send the inputs to the Initiator */ + subFlow(new SendStateAndRefFlow(counterpartySession, inputsAndOutputs.getFirst())); + + /* Send the output generated from the fiat currency move proposal to the initiator */ + counterpartySession.send(inputsAndOutputs.getSecond()); + subFlow(new SignTransactionFlow(counterpartySession) { + @Override + protected void checkTransaction(@NotNull SignedTransaction stx) throws FlowException { + // Custom Logic to validate transaction. + } + }); + return subFlow(new ReceiveFinalityFlow(counterpartySession)); + } +} From 6c47ec6fcf8bac516e46b3daf8c7bd5c752508a6 Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Thu, 4 Jan 2024 13:56:27 +0800 Subject: [PATCH 07/25] add burn token API endpoint --- .../samples/example/webserver/Controller.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java b/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java index 7da08e3b..7a2819e9 100644 --- a/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java +++ b/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java @@ -34,6 +34,7 @@ import org.springframework.web.bind.annotation.RestController; import net.corda.samples.dollartohousetoken.flows.FiatCurrencyIssueFlow; +import net.corda.samples.dollartohousetoken.flows.FiatCurrencyMoveFlow; /** * Define your API endpoints here. @@ -170,4 +171,37 @@ public ResponseEntity issueIOU(HttpServletRequest request) throws Illega .body(e.getMessage()); } } + + @PostMapping(value = "burn-token", produces = TEXT_PLAIN_VALUE, headers = "Content-Type=application/x-www-form-urlencoded") + public ResponseEntity burnIOU(HttpServletRequest request) throws IllegalArgumentException { + + Long amount = Long.valueOf(request.getParameter("amount")); + String party = request.getParameter("recipient"); + String currency = request.getParameter("currency"); + // Get party objects for recipient and the currency. + System.out.println(amount); + System.out.println(currency); + System.out.println(party); + CordaX500Name partyX500Name = CordaX500Name.parse(party); + Party otherParty = proxy.wellKnownPartyFromX500Name(partyX500Name); + + // Create a new token state using the parameters given. + try { + // Start the IssueFlow. We block and waits for the flow to return. + System.out.println("burning token"); + SignedTransaction result = proxy.startTrackedFlowDynamic(FiatCurrencyMoveFlow.class, currency, amount, otherParty) + .getReturnValue().get(); + // Return the response. + return ResponseEntity + .status(HttpStatus.CREATED) + .body("Transaction id " + result.getId() + " committed to ledger.\n " + + result.getTx().getOutput(0)); + // For the purposes of this demo app, we do not differentiate by exception type. + } catch (Exception e) { + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(e.getMessage()); + } + } + } \ No newline at end of file From 84d0ae011b5699bcef6781e96e8d6f62e8ae48f8 Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Thu, 4 Jan 2024 16:24:54 +0800 Subject: [PATCH 08/25] add create token, burn token APIs --- Tokens/dollartohousetoken/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tokens/dollartohousetoken/README.md b/Tokens/dollartohousetoken/README.md index 9ee5b74b..03b44896 100644 --- a/Tokens/dollartohousetoken/README.md +++ b/Tokens/dollartohousetoken/README.md @@ -83,4 +83,6 @@ In one terminal: java -jar clients/build/libs/clients-1.0.jar --server.port=50005 --config.rpc.host=localhost --config.rpc.port=10006 --config.rpc.username=user1 --config.rpc.password=test In another terminal: curl -i -X GET http://localhost:50005/me -H 'Content-Type: application/json' -curl -i -X POST 'http://localhost:50005/create-token?amount=100&recipient=O=PartyC,L=Mumbai,C=IN¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' \ No newline at end of file +curl -i -X POST 'http://localhost:50005/create-token?amount=100&recipient=O=PartyC,L=Mumbai,C=IN¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' +curl -i -X POST 'http://localhost:50005/create-token?amount=100&recipient=O=PartyA,L=London,C=GB¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' +curl -i -X POST 'http://localhost:50005/burn-token?amount=100&recipient=O=PartyC,L=Mumbai,C=IN¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' \ No newline at end of file From 3eb3c913c2cc5b7062d9fecf91e0a8a79cf196b7 Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Thu, 4 Jan 2024 16:25:12 +0800 Subject: [PATCH 09/25] add query token code --- .../flows/FiatCurrencyQuery.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java diff --git a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java new file mode 100644 index 00000000..b3d4bc48 --- /dev/null +++ b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java @@ -0,0 +1,60 @@ +package net.corda.samples.dollartohousetoken.flows; + +import co.paralleluniverse.fibers.Suspendable; +import com.r3.corda.lib.tokens.contracts.states.FungibleToken; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.identity.Party; +import net.corda.core.contracts.*; +import net.corda.core.crypto.CryptoUtils; +import net.corda.core.crypto.SecureHash; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.messaging.DataFeed; +import net.corda.core.node.ServicesForResolution; +import net.corda.core.node.services.AttachmentStorage; +import net.corda.core.node.services.IdentityService; +import net.corda.core.node.services.VaultService; +import net.corda.core.node.services.vault.AttachmentQueryCriteria.AttachmentsQueryCriteria; +import net.corda.core.node.services.vault.*; +import net.corda.core.node.services.vault.QueryCriteria.LinearStateQueryCriteria; +import net.corda.core.node.services.vault.QueryCriteria.VaultCustomQueryCriteria; +import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria; + +import java.util.*; + +@StartableByRPC +public class FiatCurrencyQuery extends FlowLogic{ + private final String currency; + private final Party recipient; + + public FiatCurrencyQuery(String currency, Party recipient) { + this.currency = currency; + this.recipient = recipient; + } + + @Override + @Suspendable + public String call() throws FlowException { + + FungibleToken receivedToken = null; + try { + + VaultQueryCriteria inputQueryCriteria = new VaultQueryCriteria(Vault.StateStatus.CONSUMED); + + receivedToken = getServiceHub().getVaultService().queryBy(FungibleToken.class,inputQueryCriteria).getStates().get(0).getState().getData(); + }catch (NoSuchElementException e){ + return "\nERROR: Your Token ID Cannot Be Found In The System"; + } + String tokenTypeId = receivedToken.getTokenType().getTokenIdentifier(); + String amoString = receivedToken.getAmount().toString(); + + { + return "\nthe Token id: " + tokenTypeId + + "\nAmount: " + amoString; + } + } +} From 2052dde9c7cdf40bff639a5084a244af9063b6cf Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Thu, 4 Jan 2024 20:37:40 +0800 Subject: [PATCH 10/25] use UNCONSUMED state --- .../samples/dollartohousetoken/flows/FiatCurrencyQuery.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java index b3d4bc48..b13109e8 100644 --- a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java +++ b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java @@ -43,7 +43,7 @@ public String call() throws FlowException { FungibleToken receivedToken = null; try { - VaultQueryCriteria inputQueryCriteria = new VaultQueryCriteria(Vault.StateStatus.CONSUMED); + VaultQueryCriteria inputQueryCriteria = new VaultQueryCriteria(Vault.StateStatus.UNCONSUMED); receivedToken = getServiceHub().getVaultService().queryBy(FungibleToken.class,inputQueryCriteria).getStates().get(0).getState().getData(); }catch (NoSuchElementException e){ @@ -53,7 +53,7 @@ public String call() throws FlowException { String amoString = receivedToken.getAmount().toString(); { - return "\nthe Token id: " + tokenTypeId + + return "\nthe Token type: " + tokenTypeId + "\nAmount: " + amoString; } } From 4cb6655cd5337bb664ac9ddba2b0e3aad504a081 Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Thu, 4 Jan 2024 20:38:00 +0800 Subject: [PATCH 11/25] add query token API endpoint --- .../samples/example/webserver/Controller.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java b/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java index 7a2819e9..7278cc6f 100644 --- a/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java +++ b/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java @@ -35,6 +35,7 @@ import net.corda.samples.dollartohousetoken.flows.FiatCurrencyIssueFlow; import net.corda.samples.dollartohousetoken.flows.FiatCurrencyMoveFlow; +import net.corda.samples.dollartohousetoken.flows.FiatCurrencyQuery; /** * Define your API endpoints here. @@ -204,4 +205,33 @@ public ResponseEntity burnIOU(HttpServletRequest request) throws Illegal } } + + @PostMapping(value = "query-token", produces = TEXT_PLAIN_VALUE, headers = "Content-Type=application/x-www-form-urlencoded") + public ResponseEntity queryIOU(HttpServletRequest request) throws IllegalArgumentException { + + String party = request.getParameter("recipient"); + String currency = request.getParameter("currency"); + // Get party objects for recipient and the currency. + System.out.println(currency); + System.out.println(party); + CordaX500Name partyX500Name = CordaX500Name.parse(party); + Party otherParty = proxy.wellKnownPartyFromX500Name(partyX500Name); + + // Create a new token state using the parameters given. + try { + // Start the IssueFlow. We block and waits for the flow to return. + String result = proxy.startTrackedFlowDynamic(FiatCurrencyQuery.class, currency, otherParty) + .getReturnValue().get(); + // Return the response. + return ResponseEntity + .status(HttpStatus.CREATED) + .body("Transaction: " + result + "\n "); + // For the purposes of this demo app, we do not differentiate by exception type. + } catch (Exception e) { + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(e.getMessage()); + } + } + } \ No newline at end of file From 59d3063de37ca5d903926feda70da5a289f342f6 Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Thu, 4 Jan 2024 20:38:40 +0800 Subject: [PATCH 12/25] update readme --- Tokens/dollartohousetoken/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tokens/dollartohousetoken/README.md b/Tokens/dollartohousetoken/README.md index 03b44896..3ecb04c5 100644 --- a/Tokens/dollartohousetoken/README.md +++ b/Tokens/dollartohousetoken/README.md @@ -85,4 +85,6 @@ In another terminal: curl -i -X GET http://localhost:50005/me -H 'Content-Type: application/json' curl -i -X POST 'http://localhost:50005/create-token?amount=100&recipient=O=PartyC,L=Mumbai,C=IN¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' curl -i -X POST 'http://localhost:50005/create-token?amount=100&recipient=O=PartyA,L=London,C=GB¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' -curl -i -X POST 'http://localhost:50005/burn-token?amount=100&recipient=O=PartyC,L=Mumbai,C=IN¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' \ No newline at end of file +curl -i -X POST 'http://localhost:50005/burn-token?amount=100&recipient=O=PartyC,L=Mumbai,C=IN¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' +curl -i -X POST 'http://localhost:50005/burn-token?amount=100&recipient=O=PartyA,L=London,C=GB¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' +curl -i -X POST 'http://localhost:50005/query-token?recipient=O=PartyC,L=Mumbai,C=IN¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' \ No newline at end of file From ca4b3793985758db24ad2f329e5411569dcccb15 Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Thu, 4 Jan 2024 20:39:15 +0800 Subject: [PATCH 13/25] use MoveFungibleTokens subFlow --- .../flows/FiatCurrencyMoveFlow.java | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyMoveFlow.java b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyMoveFlow.java index 924ccf2b..1ac53d74 100644 --- a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyMoveFlow.java +++ b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyMoveFlow.java @@ -7,6 +7,7 @@ import com.r3.corda.lib.tokens.selection.database.selector.DatabaseTokenSelection; import com.r3.corda.lib.tokens.workflows.types.PartyAndAmount; import kotlin.Pair; +import com.r3.corda.lib.tokens.workflows.flows.rpc.MoveFungibleTokens; import net.corda.core.contracts.Amount; import net.corda.core.contracts.StateAndRef; import net.corda.core.flows.*; @@ -44,17 +45,27 @@ public SignedTransaction call() throws FlowException { /* Create instance of the fiat currency token amount */ Amount priceToken = new Amount<>(longAmount, FiatCurrency.Companion.getInstance(this.currency)); + System.out.println("recipient: " + this.recipient); + System.out.println("amount: " + this.amount); + System.out.println("longAmount: " + longAmount); + System.out.println("currency: " + this.currency); + System.out.println("priceToken: " + priceToken); + /* Generate the move proposal, it returns the input-output pair for the fiat currency transfer, which we need to send to the Initiator */ Pair>, List> inputsAndOutputs = new DatabaseTokenSelection(getServiceHub()) // here we are generating input and output states which send the correct amount to the seller, and any change back to buyer .generateMove(Collections.singletonList(new Pair<>(this.recipient, priceToken)), getOurIdentity()); - FlowSession counterpartySession = initiateFlow(this.recipient); - /* Call SendStateAndRefFlow to send the inputs to the Initiator */ + System.out.println("inputsAndOutputs: " + inputsAndOutputs); + + //FlowSession counterpartySession = initiateFlow(this.recipient); + //System.out.println("counterpartySession: " + counterpartySession); + /* + // Call SendStateAndRefFlow to send the inputs to the Initiator subFlow(new SendStateAndRefFlow(counterpartySession, inputsAndOutputs.getFirst())); - /* Send the output generated from the fiat currency move proposal to the initiator */ + // Send the output generated from the fiat currency move proposal to the initiator counterpartySession.send(inputsAndOutputs.getSecond()); subFlow(new SignTransactionFlow(counterpartySession) { @Override @@ -63,5 +74,8 @@ protected void checkTransaction(@NotNull SignedTransaction stx) throws FlowExcep } }); return subFlow(new ReceiveFinalityFlow(counterpartySession)); + */ + + return subFlow(new MoveFungibleTokens(priceToken, this.recipient)); } } From 0289e79cca3966f465d937aca52db6f5d94be959 Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Thu, 4 Jan 2024 20:55:33 +0800 Subject: [PATCH 14/25] update readme --- Tokens/dollartohousetoken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tokens/dollartohousetoken/README.md b/Tokens/dollartohousetoken/README.md index 3ecb04c5..53c2bd12 100644 --- a/Tokens/dollartohousetoken/README.md +++ b/Tokens/dollartohousetoken/README.md @@ -87,4 +87,4 @@ curl -i -X POST 'http://localhost:50005/create-token?amount=100&recipient=O=Part curl -i -X POST 'http://localhost:50005/create-token?amount=100&recipient=O=PartyA,L=London,C=GB¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' curl -i -X POST 'http://localhost:50005/burn-token?amount=100&recipient=O=PartyC,L=Mumbai,C=IN¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' curl -i -X POST 'http://localhost:50005/burn-token?amount=100&recipient=O=PartyA,L=London,C=GB¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' -curl -i -X POST 'http://localhost:50005/query-token?recipient=O=PartyC,L=Mumbai,C=IN¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' \ No newline at end of file +curl -i -X POST 'http://localhost:50005/query-token' -H 'Content-Type: application/x-www-form-urlencoded' \ No newline at end of file From 37235de4796ec4fcab6216c81a584606b58df8d9 Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Thu, 4 Jan 2024 20:55:48 +0800 Subject: [PATCH 15/25] remove paramater from query token --- .../net/corda/samples/example/webserver/Controller.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java b/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java index 7278cc6f..2c5c54fc 100644 --- a/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java +++ b/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java @@ -209,8 +209,12 @@ public ResponseEntity burnIOU(HttpServletRequest request) throws Illegal @PostMapping(value = "query-token", produces = TEXT_PLAIN_VALUE, headers = "Content-Type=application/x-www-form-urlencoded") public ResponseEntity queryIOU(HttpServletRequest request) throws IllegalArgumentException { - String party = request.getParameter("recipient"); - String currency = request.getParameter("currency"); + //String party = request.getParameter("recipient"); + //String currency = request.getParameter("currency"); + // just hardcode the party and currency for now + String party = "O=PartyB,L=New York,C=US"; + String currency = "USD"; + // Get party objects for recipient and the currency. System.out.println(currency); System.out.println(party); From af42bf26b954e54aa49c97f79c89432ee1d8bb32 Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Thu, 4 Jan 2024 20:56:10 +0800 Subject: [PATCH 16/25] add holder to query token return info --- .../samples/dollartohousetoken/flows/FiatCurrencyQuery.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java index b13109e8..65f0bb68 100644 --- a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java +++ b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java @@ -51,10 +51,12 @@ public String call() throws FlowException { } String tokenTypeId = receivedToken.getTokenType().getTokenIdentifier(); String amoString = receivedToken.getAmount().toString(); + String holder = receivedToken.getHolder().toString(); { return "\nthe Token type: " + tokenTypeId + - "\nAmount: " + amoString; + "\nAmount: " + amoString + + "\nHolder: " + holder; } } } From 62c4fe31c70895e9ff656204edc5cf172a95840c Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Fri, 5 Jan 2024 19:45:25 +0800 Subject: [PATCH 17/25] update readme --- Tokens/dollartohousetoken/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Tokens/dollartohousetoken/README.md b/Tokens/dollartohousetoken/README.md index 53c2bd12..3538b38f 100644 --- a/Tokens/dollartohousetoken/README.md +++ b/Tokens/dollartohousetoken/README.md @@ -83,6 +83,7 @@ In one terminal: java -jar clients/build/libs/clients-1.0.jar --server.port=50005 --config.rpc.host=localhost --config.rpc.port=10006 --config.rpc.username=user1 --config.rpc.password=test In another terminal: curl -i -X GET http://localhost:50005/me -H 'Content-Type: application/json' +curl -i -X GET http://localhost:50005/states -H 'Content-Type: application/json' curl -i -X POST 'http://localhost:50005/create-token?amount=100&recipient=O=PartyC,L=Mumbai,C=IN¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' curl -i -X POST 'http://localhost:50005/create-token?amount=100&recipient=O=PartyA,L=London,C=GB¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' curl -i -X POST 'http://localhost:50005/burn-token?amount=100&recipient=O=PartyC,L=Mumbai,C=IN¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' From d021eb33d3b7128b0c96620da75d62d4be2c2e11 Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Fri, 5 Jan 2024 19:45:38 +0800 Subject: [PATCH 18/25] add token contracts --- Tokens/dollartohousetoken/workflows/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/Tokens/dollartohousetoken/workflows/build.gradle b/Tokens/dollartohousetoken/workflows/build.gradle index 12db9775..a3ec8c83 100644 --- a/Tokens/dollartohousetoken/workflows/build.gradle +++ b/Tokens/dollartohousetoken/workflows/build.gradle @@ -52,6 +52,7 @@ dependencies { // Token SDK dependencies. cordaCompile "$tokens_release_group:tokens-workflows:$tokens_release_version" + cordaCompile "$tokens_release_group:tokens-contracts:$tokens_release_version" } task integrationTest(type: Test, dependsOn: []) { From 5024157a59213bedfdfeb0e6bfda2942df566785 Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Fri, 5 Jan 2024 19:45:52 +0800 Subject: [PATCH 19/25] update issue tokens --- .../flows/FiatCurrencyIssueFlow.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyIssueFlow.java b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyIssueFlow.java index 634d9b3e..f7458fb9 100644 --- a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyIssueFlow.java +++ b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyIssueFlow.java @@ -7,6 +7,7 @@ import com.r3.corda.lib.tokens.contracts.types.TokenType; import com.r3.corda.lib.tokens.money.FiatCurrency; import com.r3.corda.lib.tokens.money.MoneyUtilities; +import com.r3.corda.lib.tokens.contracts.utilities.AmountUtilities; import com.r3.corda.lib.tokens.workflows.flows.rpc.IssueTokens; import com.r3.corda.lib.tokens.workflows.utilities.FungibleTokenBuilder; import net.corda.core.contracts.Amount; @@ -17,6 +18,7 @@ import net.corda.core.identity.Party; import net.corda.core.transactions.SignedTransaction; import org.intellij.lang.annotations.Flow; +import java.util.Collections; /** * Flow class to issue fiat currency. FiatCurrency is defined in the Token SDK and is issued as a Fungible Token. @@ -51,8 +53,17 @@ public SignedTransaction call() throws FlowException { .heldBy(recipient) .buildFungibleToken(); + final IssuedTokenType targetTokenType = new IssuedTokenType(getOurIdentity(), tokenType); + + final Amount targetAmount = AmountUtilities.amount(amount, targetTokenType); + final FungibleToken targetToken = new FungibleToken(targetAmount, recipient, null); + /* Issue the required amount of the token to the recipient */ - return subFlow(new IssueTokens(ImmutableList.of(fungibleToken), ImmutableList.of(recipient))); + //return subFlow(new IssueTokens(ImmutableList.of(fungibleToken), ImmutableList.of(recipient))); + + return subFlow(new IssueTokens( + Collections.singletonList(targetToken), // Output instances + Collections.emptyList())); } private TokenType getTokenType() throws FlowException{ From 3dca341abded7ab3c961938ad4eccc34d9a5f41e Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Fri, 5 Jan 2024 19:46:05 +0800 Subject: [PATCH 20/25] update burn tokens --- .../flows/FiatCurrencyMoveFlow.java | 70 +++++++++++++------ 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyMoveFlow.java b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyMoveFlow.java index 1ac53d74..d9e6d595 100644 --- a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyMoveFlow.java +++ b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyMoveFlow.java @@ -4,6 +4,11 @@ import com.r3.corda.lib.tokens.contracts.states.FungibleToken; import com.r3.corda.lib.tokens.contracts.types.TokenType; import com.r3.corda.lib.tokens.money.FiatCurrency; +import com.r3.corda.lib.tokens.money.MoneyUtilities; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.node.services.vault.QueryCriteria; +import com.r3.corda.lib.tokens.contracts.utilities.AmountUtilities; +import com.r3.corda.lib.tokens.workflows.utilities.QueryUtilities; import com.r3.corda.lib.tokens.selection.database.selector.DatabaseTokenSelection; import com.r3.corda.lib.tokens.workflows.types.PartyAndAmount; import kotlin.Pair; @@ -43,39 +48,62 @@ public SignedTransaction call() throws FlowException { Optional optFoo = Optional.ofNullable(null); long longAmount = optFoo.orElse( this.amount ); /* Create instance of the fiat currency token amount */ - Amount priceToken = new Amount<>(longAmount, FiatCurrency.Companion.getInstance(this.currency)); + Amount tokenAmount = new Amount<>(longAmount, getTokenType()); System.out.println("recipient: " + this.recipient); System.out.println("amount: " + this.amount); System.out.println("longAmount: " + longAmount); System.out.println("currency: " + this.currency); - System.out.println("priceToken: " + priceToken); + System.out.println("tokenAmount: " + tokenAmount); /* Generate the move proposal, it returns the input-output pair for the fiat currency transfer, which we need to send to the Initiator */ Pair>, List> inputsAndOutputs = new DatabaseTokenSelection(getServiceHub()) // here we are generating input and output states which send the correct amount to the seller, and any change back to buyer - .generateMove(Collections.singletonList(new Pair<>(this.recipient, priceToken)), getOurIdentity()); + .generateMove(Collections.singletonList(new Pair<>(this.recipient, tokenAmount)), getOurIdentity()); System.out.println("inputsAndOutputs: " + inputsAndOutputs); - //FlowSession counterpartySession = initiateFlow(this.recipient); - //System.out.println("counterpartySession: " + counterpartySession); - /* - // Call SendStateAndRefFlow to send the inputs to the Initiator - subFlow(new SendStateAndRefFlow(counterpartySession, inputsAndOutputs.getFirst())); - - // Send the output generated from the fiat currency move proposal to the initiator - counterpartySession.send(inputsAndOutputs.getSecond()); - subFlow(new SignTransactionFlow(counterpartySession) { - @Override - protected void checkTransaction(@NotNull SignedTransaction stx) throws FlowException { - // Custom Logic to validate transaction. - } - }); - return subFlow(new ReceiveFinalityFlow(counterpartySession)); - */ - - return subFlow(new MoveFungibleTokens(priceToken, this.recipient)); + final TokenType usdTokenType = FiatCurrency.Companion.getInstance("USD"); + final Party partyA = getServiceHub().getNetworkMapCache().getPeerByLegalName( + CordaX500Name.parse("O=PartyA,L=London,C=GB")); + final Party holderMint = getServiceHub().getNetworkMapCache().getPeerByLegalName( + CordaX500Name.parse("O=PartyA,L=London,C=GB")); + if (holderMint == null) + throw new FlowException("No Mint found"); + + // Who is going to own the output, and how much? + final Party partyC = getServiceHub().getNetworkMapCache().getPeerByLegalName( + CordaX500Name.parse("O=PartyC, L=Mumbai, C=IN")); + final Amount fiftyUSD = AmountUtilities.amount(50L, usdTokenType); + final PartyAndAmount fiftyUSDForPartyC = new PartyAndAmount<>(partyC, tokenAmount); + + // Describe how to find those $ held by PartyA. + final QueryCriteria issuedByHolderMint = QueryUtilities.tokenAmountWithIssuerCriteria(usdTokenType, holderMint); + final QueryCriteria heldByPartyA = QueryUtilities.heldTokenAmountCriteria(usdTokenType, partyA); + + // Do the move + final SignedTransaction moveTx = subFlow(new MoveFungibleTokens( + Collections.singletonList(fiftyUSDForPartyC), // Output instances + Collections.emptyList(), // Observers + issuedByHolderMint.and(heldByPartyA), // Criteria to find the inputs + partyA)); // change holder + return moveTx; + } + + private TokenType getTokenType() throws FlowException { + switch (currency) { + case "USD": + return MoneyUtilities.getUSD(); + + case "GBP": + return MoneyUtilities.getGBP(); + + case "EUR": + return MoneyUtilities.getEUR(); + + default: + throw new FlowException("Currency Not Supported"); + } } } From 091ccb29d8a7dcf73f249370aac87a89545b25f7 Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Fri, 5 Jan 2024 20:56:56 +0800 Subject: [PATCH 21/25] update query token API, return the last entry in the list --- .../dollartohousetoken/flows/FiatCurrencyQuery.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java index 65f0bb68..88106479 100644 --- a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java +++ b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java @@ -46,6 +46,16 @@ public String call() throws FlowException { VaultQueryCriteria inputQueryCriteria = new VaultQueryCriteria(Vault.StateStatus.UNCONSUMED); receivedToken = getServiceHub().getVaultService().queryBy(FungibleToken.class,inputQueryCriteria).getStates().get(0).getState().getData(); + + List> arrList = getServiceHub().getVaultService().queryBy(FungibleToken.class,inputQueryCriteria).getStates(); + ListIterator> iterator = arrList.listIterator(); + StateAndRef valueRet; + while (iterator.hasNext()) { + //System.out.println("Value is : " + iterator.next()); + valueRet = iterator.next(); + System.out.println("Value :" + valueRet.getState().getData()); + receivedToken = valueRet.getState().getData(); + } }catch (NoSuchElementException e){ return "\nERROR: Your Token ID Cannot Be Found In The System"; } From 4bf755205f1284132233c872a9b437c31f958c92 Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Sat, 6 Jan 2024 21:37:45 +0800 Subject: [PATCH 22/25] use dynamic array in received token --- .../flows/FiatCurrencyQuery.java | 50 +++++++++++++------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java index 88106479..5d3d79fa 100644 --- a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java +++ b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java @@ -40,33 +40,55 @@ public FiatCurrencyQuery(String currency, Party recipient) { @Suspendable public String call() throws FlowException { - FungibleToken receivedToken = null; + FungibleToken[] receivedToken; + String[] tokenTypeId; + String[] amoString; + String[] holder; + int i = 0; + String retString = ""; + try { VaultQueryCriteria inputQueryCriteria = new VaultQueryCriteria(Vault.StateStatus.UNCONSUMED); - receivedToken = getServiceHub().getVaultService().queryBy(FungibleToken.class,inputQueryCriteria).getStates().get(0).getState().getData(); - List> arrList = getServiceHub().getVaultService().queryBy(FungibleToken.class,inputQueryCriteria).getStates(); ListIterator> iterator = arrList.listIterator(); StateAndRef valueRet; + + int arraySize = 1; + receivedToken = new FungibleToken[arraySize]; + tokenTypeId = new String[arraySize]; + amoString = new String[arraySize]; + holder = new String[arraySize]; + while (iterator.hasNext()) { //System.out.println("Value is : " + iterator.next()); valueRet = iterator.next(); System.out.println("Value :" + valueRet.getState().getData()); - receivedToken = valueRet.getState().getData(); + + if (receivedToken.length == i) { + // expand list + receivedToken = Arrays.copyOf(receivedToken, receivedToken.length + arraySize); + amoString = Arrays.copyOf(amoString, amoString.length + arraySize); + tokenTypeId = Arrays.copyOf(tokenTypeId, tokenTypeId.length + arraySize); + holder = Arrays.copyOf(holder, holder.length + arraySize); + } + receivedToken[i] = valueRet.getState().getData(); + + tokenTypeId[i] = receivedToken[i].getTokenType().getTokenIdentifier(); + amoString[i] = receivedToken[i].getAmount().toString(); + holder[i] = receivedToken[i].getHolder().toString(); + + retString = "\nthe Token type: " + tokenTypeId[i] + + "\nAmount: " + amoString[i] + + "\nHolder: " + holder[i]; + i++; } - }catch (NoSuchElementException e){ - return "\nERROR: Your Token ID Cannot Be Found In The System"; - } - String tokenTypeId = receivedToken.getTokenType().getTokenIdentifier(); - String amoString = receivedToken.getAmount().toString(); - String holder = receivedToken.getHolder().toString(); - { - return "\nthe Token type: " + tokenTypeId + - "\nAmount: " + amoString + - "\nHolder: " + holder; + return retString; + + } catch (NoSuchElementException e) { + return "\nERROR: Your Token ID Cannot Be Found In The System"; } } } From 498ab2cee68c662524164154f2613d3e6a656ca2 Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Mon, 8 Jan 2024 08:28:42 +0800 Subject: [PATCH 23/25] update query token display --- .../dollartohousetoken/flows/FiatCurrencyQuery.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java index 5d3d79fa..3476d422 100644 --- a/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java +++ b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java @@ -79,9 +79,10 @@ public String call() throws FlowException { amoString[i] = receivedToken[i].getAmount().toString(); holder[i] = receivedToken[i].getHolder().toString(); - retString = "\nthe Token type: " + tokenTypeId[i] + - "\nAmount: " + amoString[i] + - "\nHolder: " + holder[i]; + retString += "\n" + i + + ") Token type: " + tokenTypeId[i] + + ", Amount: " + amoString[i] + + ", Holder: " + holder[i]; i++; } From 97a0a3367e071ce518ba8e9f33d1a43fb72892f8 Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Mon, 8 Jan 2024 08:43:09 +0800 Subject: [PATCH 24/25] add whitespace --- Tokens/dollartohousetoken/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tokens/dollartohousetoken/README.md b/Tokens/dollartohousetoken/README.md index 3538b38f..cc46c667 100644 --- a/Tokens/dollartohousetoken/README.md +++ b/Tokens/dollartohousetoken/README.md @@ -84,8 +84,8 @@ java -jar clients/build/libs/clients-1.0.jar --server.port=50005 --config.rpc.ho In another terminal: curl -i -X GET http://localhost:50005/me -H 'Content-Type: application/json' curl -i -X GET http://localhost:50005/states -H 'Content-Type: application/json' -curl -i -X POST 'http://localhost:50005/create-token?amount=100&recipient=O=PartyC,L=Mumbai,C=IN¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' -curl -i -X POST 'http://localhost:50005/create-token?amount=100&recipient=O=PartyA,L=London,C=GB¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' -curl -i -X POST 'http://localhost:50005/burn-token?amount=100&recipient=O=PartyC,L=Mumbai,C=IN¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' -curl -i -X POST 'http://localhost:50005/burn-token?amount=100&recipient=O=PartyA,L=London,C=GB¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' -curl -i -X POST 'http://localhost:50005/query-token' -H 'Content-Type: application/x-www-form-urlencoded' \ No newline at end of file +curl -i -X POST 'http://localhost:50005/create-token?amount=100&recipient=O=PartyC,L=Mumbai,C=IN¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' +curl -i -X POST 'http://localhost:50005/create-token?amount=100&recipient=O=PartyA,L=London,C=GB¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' +curl -i -X POST 'http://localhost:50005/burn-token?amount=100&recipient=O=PartyC,L=Mumbai,C=IN¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' +curl -i -X POST 'http://localhost:50005/burn-token?amount=100&recipient=O=PartyA,L=London,C=GB¤cy=USD' -H 'Content-Type: application/x-www-form-urlencoded' +curl -i -X POST 'http://localhost:50005/query-token' -H 'Content-Type: application/x-www-form-urlencoded' \ No newline at end of file From dacb3d5de2b7dadda2da46df6116d858637e02e5 Mon Sep 17 00:00:00 2001 From: yeosiowvic Date: Mon, 8 Jan 2024 08:43:59 +0800 Subject: [PATCH 25/25] add title --- Tokens/dollartohousetoken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tokens/dollartohousetoken/README.md b/Tokens/dollartohousetoken/README.md index cc46c667..398f6614 100644 --- a/Tokens/dollartohousetoken/README.md +++ b/Tokens/dollartohousetoken/README.md @@ -77,7 +77,7 @@ We could now verify that the non-fungible token has been transferred to PartyC a run vaultQuery contractStateType: com.r3.corda.lib.tokens.contracts.states.NonFungibleToken -Use RPC client +## Use RPC client In one terminal: ./gradlew assemble java -jar clients/build/libs/clients-1.0.jar --server.port=50005 --config.rpc.host=localhost --config.rpc.port=10006 --config.rpc.username=user1 --config.rpc.password=test