diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..1bec35e --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 4702e35..2d79f63 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,11 @@ buildscript { ext { corda_release_version = cordaVersion + tokens_release_version = '1.1' + tokens_release_group = 'com.r3.corda.lib.tokens' + confidential_id_release_version = '1.0' + confidential_id_release_group = 'com.r3.corda.lib.ci' + } repositories { @@ -29,6 +34,8 @@ allprojects { //Properties that you need to compile your project (The applicatio mavenCentral() maven { url 'https://software.r3.com/artifactory/corda' } maven { url 'https://jitpack.io' } + maven { url 'https://software.r3.com/artifactory/corda-lib' } + maven { url 'https://software.r3.com/artifactory/corda-lib-dev' } } tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) { @@ -84,6 +91,12 @@ dependencies { cordaCompile "org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion" cordaCompile "org.apache.logging.log4j:log4j-web:$log4jVersion" cordaCompile "org.slf4j:jul-to-slf4j:$slf4jVersion" + + // Token SDK dependencies. + cordapp "$tokens_release_group:tokens-contracts:$tokens_release_version" + cordapp "$tokens_release_group:tokens-workflows:$tokens_release_version" + cordapp "$tokens_release_group:tokens-money:$tokens_release_version" + cordapp "$tokens_release_group:tokens-selection:$tokens_release_version" } task installQuasar(type: Copy) { @@ -108,6 +121,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { cordapp project(':contracts') cordapp project(':workflows') cordapp project(':cbdc-bridge') + cordapp("$tokens_release_group:tokens-contracts:$tokens_release_version") + cordapp("$tokens_release_group:tokens-workflows:$tokens_release_version") + cordapp("$tokens_release_group:tokens-money:$tokens_release_version") + cordapp("$tokens_release_group:tokens-selection:$tokens_release_version") // This configuration is for any CorDapps with custom schema, We will leave this as true to avoid // problems for developers who are not familiar with Corda. If you are not using custom schemas, you can change // it to false for quicker project compiling time. diff --git a/clients/src/main/kotlin/com/cbdc/industria/tech/webserver/Controller.kt b/clients/src/main/kotlin/com/cbdc/industria/tech/webserver/Controller.kt index 10ba4cb..ac841b9 100644 --- a/clients/src/main/kotlin/com/cbdc/industria/tech/webserver/Controller.kt +++ b/clients/src/main/kotlin/com/cbdc/industria/tech/webserver/Controller.kt @@ -1,16 +1,20 @@ package com.cbdc.industria.tech.webserver -import com.cbdc.industria.tech.flows.CheckPings +import com.cbdc.industria.tech.flows.* import com.cbdc.industria.tech.webserver.rpc.NodeRPCConnection +import net.corda.core.identity.CordaX500Name import net.corda.core.messaging.startFlow import net.corda.core.utilities.getOrThrow import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RestController @RestController class Controller { + val SERVICE_NAMES = listOf("Notary", "Network Map Service") + @Autowired lateinit var node : NodeRPCConnection @@ -19,8 +23,51 @@ class Controller { return "Greetings from Spring Boot!"; } + /** + * Returns the node's name. + */ + @GetMapping("/me") + fun whoami() = mapOf("me" to node.proxy.nodeInfo().legalIdentities.first().name) + + /** + * Returns all parties registered with the network map service. These names can be used to look up identities using + * the identity service. + */ + @GetMapping("/peers") + fun getPeers(): Map> { + val nodeInfo = node.proxy.networkMapSnapshot() + return mapOf("peers" to nodeInfo + .map { it.legalIdentities.first().name } + //filter out myself, notary and eventual network map started by driver + .filter { it.organisation !in (SERVICE_NAMES + node.proxy.nodeInfo().legalIdentities.first().name.organisation) }) + } + @GetMapping("/ping") fun ping() : List{ return node.proxy.startFlow(::CheckPings).returnValue.getOrThrow() } + + @GetMapping("/create") + fun createEGBP():String{ + return node.proxy.startFlow(::CreateHouseTokenFlow, "EGBP", 1).returnValue.getOrThrow() + } + + @GetMapping("/issue") + fun issueEGBP():String{ + val party = node.proxy.nodeInfo().legalIdentities.first() + return node.proxy.startFlow(::IssueHouseTokenFlow, "EGBP", 100, party).returnValue.getOrThrow() + } + + @GetMapping("/balance") + fun balance(): Long{ + return node.proxy.startFlow(::GetTokenBalance, "EGBP").returnValue.getOrThrow() + } + + @GetMapping("/transfer") + fun transferCBOEtoPartyB(): String{ + val partyName = "O=PartyB,L=New York,C=US" + val partyX500Name = CordaX500Name.parse(partyName) + val holder = node.proxy.wellKnownPartyFromX500Name(partyX500Name) ?: return "Party named $partyName cannot be found." + return node.proxy.startFlow(::MoveHouseTokenFlow, "EGBP", holder, 100).returnValue.getOrThrow() + } } \ No newline at end of file diff --git a/contracts/build.gradle b/contracts/build.gradle index c42fa1a..11c500b 100644 --- a/contracts/build.gradle +++ b/contracts/build.gradle @@ -16,6 +16,10 @@ dependencies { // Corda dependencies. cordaCompile "$cordaCoreReleaseGroup:corda-core:$cordaCoreVersion" testCompile "$cordaReleaseGroup:corda-node-driver:$cordaVersion" + + cordapp "$tokens_release_group:tokens-contracts:$tokens_release_version" + cordaCompile "$tokens_release_group:tokens-selection:$tokens_release_version" + } quasar { diff --git a/contracts/src/main/kotlin/com/cbdc/industria/tech/contracts/DigitalPound.kt b/contracts/src/main/kotlin/com/cbdc/industria/tech/contracts/DigitalPound.kt new file mode 100644 index 0000000..d4398d3 --- /dev/null +++ b/contracts/src/main/kotlin/com/cbdc/industria/tech/contracts/DigitalPound.kt @@ -0,0 +1,54 @@ +package com.cbdc.industria.tech.contracts + +import com.r3.corda.lib.tokens.contracts.EvolvableTokenContract +import com.r3.corda.lib.tokens.contracts.FungibleTokenContract +import com.r3.corda.lib.tokens.contracts.states.EvolvableTokenType +import com.r3.corda.lib.tokens.contracts.states.FungibleToken +import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType +import net.corda.core.contracts.Amount +import net.corda.core.contracts.BelongsToContract +import net.corda.core.contracts.Contract +import net.corda.core.contracts.UniqueIdentifier +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.Party +import net.corda.core.transactions.LedgerTransaction + +@BelongsToContract(DigitalPoundStateContract::class) +data class DigitalPoundState( + override val amount: Amount, + override val holder: AbstractParty +):FungibleToken(amount, holder) + +class DigitalPoundStateContract : FungibleTokenContract(), Contract{ + companion object{ + const val CONTACT_ID = "com.cbdc.industria.tech.contracts.DigitalPoundStateContract" + } +} + + +class HouseTokenStateContract : EvolvableTokenContract(), Contract { + companion object { + const val CONTRACT_ID = "net.corda.samples.tokenizedhouse.contracts.HouseTokenStateContract" + } + override fun additionalCreateChecks(tx: LedgerTransaction) { + // Write contract validation logic to be performed while creation of token + val outputState = tx.getOutput(0) as FungibleHouseTokenState + outputState.apply { + require(outputState.valuation > 0) {"Valuation must be greater than zero"} + } + } + + override fun additionalUpdateChecks(tx: LedgerTransaction) { + // Write contract validation logic to be performed while updation of token + } + +} + +@BelongsToContract(HouseTokenStateContract::class) +data class FungibleHouseTokenState(val valuation: Int, + val maintainer: Party, + override val linearId: UniqueIdentifier, + override val fractionDigits: Int, + val symbol:String, + override val maintainers: List = listOf(maintainer) +) : EvolvableTokenType() \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index df36bd3..9a48fa5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -22,3 +22,8 @@ jacksonModuleKotlinVersion=2.9.7 detektReleaseGroup=io.gitlab.arturbosch.detekt detektVersion=1.16.0 + + + + + diff --git a/workflows/build.gradle b/workflows/build.gradle index fe2cbf4..2d4b0e1 100644 --- a/workflows/build.gradle +++ b/workflows/build.gradle @@ -49,6 +49,13 @@ dependencies { // CorDapp dependencies. cordapp project(":contracts") cordapp project(":cbdc-bridge") + + cordapp "$tokens_release_group:tokens-workflows:$tokens_release_version" + cordapp "$tokens_release_group:tokens-selection:$tokens_release_version" + cordapp "$confidential_id_release_group:ci-workflows:$confidential_id_release_version" + cordaCompile "$tokens_release_group:tokens-workflows:$tokens_release_version" + cordaCompile "$tokens_release_group:tokens-selection:$tokens_release_version" + } task integrationTest(type: Test, dependsOn: []) { diff --git a/workflows/src/main/kotlin/com/cbdc/industria/tech/flows/MintToken.kt b/workflows/src/main/kotlin/com/cbdc/industria/tech/flows/MintToken.kt new file mode 100644 index 0000000..ede48ad --- /dev/null +++ b/workflows/src/main/kotlin/com/cbdc/industria/tech/flows/MintToken.kt @@ -0,0 +1,154 @@ +package com.cbdc.industria.tech.flows + +import co.paralleluniverse.fibers.Suspendable +import com.cbdc.industria.tech.contracts.FungibleHouseTokenState +import com.r3.corda.lib.tokens.contracts.states.FungibleToken +import com.r3.corda.lib.tokens.contracts.types.TokenPointer +import com.r3.corda.lib.tokens.contracts.types.TokenType +import com.r3.corda.lib.tokens.contracts.utilities.issuedBy +import com.r3.corda.lib.tokens.contracts.utilities.withNotary +import com.r3.corda.lib.tokens.workflows.flows.rpc.CreateEvolvableTokens +import com.r3.corda.lib.tokens.workflows.flows.rpc.IssueTokens +import com.r3.corda.lib.tokens.workflows.flows.rpc.MoveFungibleTokens +import com.r3.corda.lib.tokens.workflows.flows.rpc.MoveFungibleTokensHandler +import com.r3.corda.lib.tokens.workflows.utilities.tokenBalance +import net.corda.core.contracts.Amount +import net.corda.core.contracts.UniqueIdentifier +import net.corda.core.flows.* +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party +import net.corda.core.utilities.ProgressTracker + + +@StartableByRPC +class MintToken: FlowLogic(){ + + @Suspendable + override fun call(): Int { + TODO() + } +} + + +@StartableByRPC +class CreateHouseTokenFlow(val symbol: String, + val valuationOfHouse:Int) : FlowLogic() { + override val progressTracker = ProgressTracker() + + @Suspendable + override fun call():String { + // Obtain a reference from a notary we wish to use. + val notary = serviceHub.networkMapCache.getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")) + + //create token type + val evolvableTokenTypeHouseState = FungibleHouseTokenState(valuationOfHouse,ourIdentity, + UniqueIdentifier(),0,symbol) + + //warp it with transaction state specifying the notary + val transactionState = evolvableTokenTypeHouseState withNotary notary!! + + //call built in sub flow CreateEvolvableTokens. This can be called via rpc or in unit testing + val stx = subFlow(CreateEvolvableTokens(transactionState)) + + return "Fungible house token $symbol has created with valuationL: $valuationOfHouse " + + "\ntxId: ${stx.id}" + } +} + + +@StartableByRPC +class IssueHouseTokenFlow(val symbol: String, + val quantity: Long, + val holder: Party +) : FlowLogic() { + override val progressTracker = ProgressTracker() + + @Suspendable + override fun call():String { + + //get house states on ledger with uuid as input tokenId + val stateAndRef = serviceHub.vaultService.queryBy(FungibleHouseTokenState::class.java) + .states.filter { it.state.data.symbol == symbol }[0] + + //get the RealEstateEvolvableTokenType object + val evolvableTokenType = stateAndRef.state.data + + //get the pointer pointer to the house + val tokenPointer: TokenPointer<*> = evolvableTokenType.toPointer(evolvableTokenType.javaClass) + + //assign the issuer to the house type who will be issuing the tokens + val issuedTokenType = tokenPointer issuedBy ourIdentity + + //specify how much amount to issue to holder + val amount = Amount(quantity,issuedTokenType) + + val fungibletoken = FungibleToken(amount,holder) + + val stx = subFlow(IssueTokens(listOf(fungibletoken))) + return "Issued $quantity $symbol token to ${holder.name.organisation}" + } +} + + +@InitiatingFlow +@StartableByRPC +class GetTokenBalance(val symbol:String) : FlowLogic() { + override val progressTracker = ProgressTracker() + + @Suspendable + override fun call():Long { + //get house states on ledger with uuid as input tokenId + val stateAndRef = serviceHub.vaultService.queryBy(FungibleHouseTokenState::class.java) + .states.filter { it.state.data.symbol == symbol }[0] + + //get the Token State object + val evolvableTokenType = stateAndRef.state.data + //get the pointer pointer to the house + val tokenPointer = evolvableTokenType.toPointer(evolvableTokenType.javaClass) + + //retrieve amount + val amount: Amount = serviceHub.vaultService.tokenBalance(tokenPointer) + + //return "\n You currently have " + amount.quantity + " " + symbol + " Tokens issued by "+evolvableTokenType.maintainer.name.organisation+"\n"; + return amount.quantity + } +} + + +@InitiatingFlow +@StartableByRPC +class MoveHouseTokenFlow(val symbol: String, + val holder: Party, + val quantity: Long) : FlowLogic() { + override val progressTracker = ProgressTracker() + + @Suspendable + override fun call():String { + //get house states on ledger with uuid as input tokenId + val stateAndRef = serviceHub.vaultService.queryBy(FungibleHouseTokenState::class.java) + .states.filter { it.state.data.symbol == symbol }[0] + + //get the RealEstateEvolvableTokenType object + val evolvableTokenType = stateAndRef.state.data + + //get the pointer pointer to the house + val tokenPointer: TokenPointer = evolvableTokenType.toPointer(evolvableTokenType.javaClass) + + //specify how much amount to issue to holder + val amount:Amount = Amount(quantity,tokenPointer) + val stx = subFlow(MoveFungibleTokens(amount,holder)) + + return "Moved $quantity $symbol token(s) to ${holder.name.organisation}"+ + "\ntxId: ${stx.id}" + } +} + +@InitiatedBy(MoveHouseTokenFlow::class) +class MoveHouseTokenFlowResponder(val counterpartySession: FlowSession) : FlowLogic() { + @Suspendable + override fun call() { + // Simply use the MoveFungibleTokensHandler as the responding flow + return subFlow(MoveFungibleTokensHandler(counterpartySession)) + + } +} \ No newline at end of file