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..398f6614 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,17 @@ 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 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 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 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..2c5c54fc --- /dev/null +++ b/Tokens/dollartohousetoken/clients/src/main/java/net/corda/samples/example/webserver/Controller.java @@ -0,0 +1,241 @@ +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; +import net.corda.samples.dollartohousetoken.flows.FiatCurrencyMoveFlow; +import net.corda.samples.dollartohousetoken.flows.FiatCurrencyQuery; + +/** + * 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 { + + 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. + SignedTransaction result = proxy.startTrackedFlowDynamic(FiatCurrencyIssueFlow.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()); + } + } + + @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()); + } + } + + + @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"); + // 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); + 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 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 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: []) { 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{ 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..d9e6d595 --- /dev/null +++ b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyMoveFlow.java @@ -0,0 +1,109 @@ +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.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; +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.*; +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 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("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, tokenAmount)), getOurIdentity()); + + System.out.println("inputsAndOutputs: " + inputsAndOutputs); + + 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"); + } + } +} 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..3476d422 --- /dev/null +++ b/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/samples/dollartohousetoken/flows/FiatCurrencyQuery.java @@ -0,0 +1,95 @@ +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; + String[] tokenTypeId; + String[] amoString; + String[] holder; + int i = 0; + String retString = ""; + + try { + + VaultQueryCriteria inputQueryCriteria = new VaultQueryCriteria(Vault.StateStatus.UNCONSUMED); + + 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()); + + 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 += "\n" + i + + ") Token type: " + tokenTypeId[i] + + ", Amount: " + amoString[i] + + ", Holder: " + holder[i]; + i++; + } + + return retString; + + } catch (NoSuchElementException e) { + return "\nERROR: Your Token ID Cannot Be Found In The System"; + } + } +}