Skip to content

Commit

Permalink
Token sample kotlin (corda#27)
Browse files Browse the repository at this point in the history
* Token Kotlin Sample
* update command in java sample
  • Loading branch information
ashutoshmeher-r3 authored Aug 31, 2023
1 parent a9e7e43 commit 635d4db
Show file tree
Hide file tree
Showing 24 changed files with 1,633 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public static class Issue implements Command { }

public static class Transfer implements Command { }

public static class Burn implements Command { }


@Override
public void verify(UtxoLedgerTransaction transaction) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,15 @@ public String call(@NotNull ClientRequestBody requestBody) {
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addInputStates(tokenClaim.getClaimedTokens().stream().map(ClaimedToken::getStateRef).collect(Collectors.toList()))
.addOutputStates(List.of(goldStateChange))
.addCommand(new GoldContract.Transfer())
.addCommand(new GoldContract.Burn())
.addSignatories(Collections.singletonList(myInfo.getLedgerKeys().get(0)));
} else {
// if there is no change, no need to create state representing the change to be given back to the sender.
txBuilder = ledgerService.createTransactionBuilder()
.setNotary(notary.getName())
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addInputStates(tokenClaim.getClaimedTokens().stream().map(ClaimedToken::getStateRef).collect(Collectors.toList()))
.addCommand(new GoldContract.Transfer())
.addCommand(new GoldContract.Burn())
.addSignatories(Collections.singletonList(myInfo.getLedgerKeys().get(0)));
}

Expand Down
134 changes: 134 additions & 0 deletions kotlin-samples/shinny-tokens/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Tokens in Next-Gen Corda

Unlike Corda 4, we don’t have an SDK for tokens in Next-Gen Corda;
the token’s functionality is brought into the core C5 platform.
We have also introduced a new Token Selection API, which enables a flow to claim
tokens exclusively and provides a way to merge and return fungible tokens satisfying a given amount.

In this sample, I will show you how you can create a gold stablecoin,
a commodity backed enterprise-grade and regulatory-friendly stablecoin
using Next-Gen Corda.

## Tokens app
In this application, we will mint gold tokens and then transfer these tokens.

In this app you can:
1. Write a flow to Create a Gold Asset/State on Ledger. `IssueGoldTokensFlow`
2. List out the gold entries you had. `ListGoldTokens`
3. Claim and transfer the tokens to a new member. `TransferGoldTokenFlow`
4. Burn tokens available with a member. `BurnGoldTokenFlow`

### Setting up

1. We will begin our test deployment with clicking the `startCorda`. This task will load up the combined Corda workers in docker.
A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v1/swagger#. You can test out some of the
functions to check connectivity. (GET /cpi function call should return an empty list as for now.)
2. We will now deploy the cordapp with a click of `5-vNodeSetup` task. Upon successful deployment of the CPI, the GET /cpi function call should now return the meta data of the cpi you just upload

### Running the tokens app

In Corda 5, flows will be triggered via `POST /flow/{holdingidentityshorthash}` and flow result will need to be view at `GET /flow/{holdingidentityshorthash}/{clientrequestid}`
* holdingidentityshorthash: the id of the network participants, ie Bob, Alice, Charlie. You can view all the short hashes of the network member with another gradle task called `ListVNodes`
* clientrequestid: the id you specify in the flow requestBody when you trigger a flow.

#### Step 1: Create Gold State
Pick a VNode identity, and get its short hash. (Let's pick Alice.).

Go to `POST /flow/{holdingidentityshorthash}`, enter the identity short hash(Alice's hash) and request body:
```
{
"clientRequestId": "issue-1",
"flowClassName": "com.r3.developers.samples.tokens.workflows.IssueGoldTokenFlow",
"requestBody": {
"symbol": "GOLD",
"owner": "CN=Bob, OU=Test Dept, O=R3, L=London, C=GB",
"amount": "20"
}
}
```

After trigger the IssueGoldTokensFlow flow, hop to `GET /flow/{holdingidentityshorthash}/{clientrequestid}` and enter the short hash(Alice's hash) and clientrequestid to view the flow result

#### Step 2: List the gold state
Go to `POST /flow/{holdingidentityshorthash}`, enter the identity short hash(Bob's hash) and request body:
```
{
"clientRequestId": "list-1",
"flowClassName": "com.r3.developers.samples.tokens.workflows.ListGoldTokens",
"requestBody": {}
}
```
After trigger the ListGoldTokens flow, again, we need to hop to `GET /flow/{holdingidentityshorthash}/{clientrequestid}`
and check the result.

#### Step 3: Transfer the gold token with `TransferGoldTokenFlow`
In this step, Bob will transfer some tokens from his vault to Charlie.
Goto `POST /flow/{holdingidentityshorthash}`, enter the identity short hash and request body.
Use Bob's holdingidentityshorthash to fire this post API.
```
{
"clientRequestId": "transfer-1",
"flowClassName": "com.r3.developers.samples.tokens.workflows.TransferGoldTokenFlow",
"requestBody": {
"symbol": "GOLD",
"issuer": "CN=Alice, OU=Test Dept, O=R3, L=London, C=GB",
"receiver": "CN=Charlie, OU=Test Dept, O=R3, L=London, C=GB",
"amount": "5"
}
}
```
And as for the result of this flow, go to `GET /flow/{holdingidentityshorthash}/{clientrequestid}` and enter the required fields.

#### Step 4: Confirm the token balances of Bob and Charlie
Go to `POST /flow/{holdingidentityshorthash}`, enter the identity short hash(Bob's hash) and request body:
```
{
"clientRequestId": "list-2",
"flowClassName": "com.r3.developers.samples.tokens.workflows.ListGoldTokens",
"requestBody": {}
}
```
Go to `POST /flow/{holdingidentityshorthash}`, enter the identity short hash(Charlie's hash) and request body:
```
{
"clientRequestId": "list-3",
"flowClassName": "com.r3.developers.samples.tokens.workflows.ListGoldTokens",
"requestBody": {}
}
```

And as for the result, you need to go to the Get API again and enter the short hash and client request ID.

#### Step 5: Burn gold token with BurnGoldTokenFlow
Go to `POST /flow/{holdingidentityshorthash}`, enter the identity short hash(Bob's hash) and request body:
```
{
"clientRequestId": "burn-1",
"flowClassName": "com.r3.developers.samples.tokens.workflows.BurnGoldTokenFlow",
"requestBody": {
"symbol": "GOLD",
"issuer": "CN=Alice, OU=Test Dept, O=R3, L=London, C=GB",
"amount": "5"
}
}
```
Go to `GET /flow/{holdingidentityshorthash}/{clientrequestid}` and enter the required fields to check the result of
the flow.

#### Step 4: Confirm the token balance of Bob

Go to `POST /flow/{holdingidentityshorthash}`, enter the identity short hash(Bob's hash) and request body:
```
{
"clientRequestId": "list-4",
"flowClassName": "com.r3.developers.samples.tokens.workflows.ListGoldTokens",
"requestBody": {}
}
```

And as for the result, you need to go to the Get API again and enter the short hash and client request ID.
Thus, we have concluded a full run through of the token app.

# Additional Information

To read more about Token Selection API, you can visit the [docs](https://docs.r3.com/en/platform/corda/5.0/developing-applications/api/ledger/utxo-ledger/token-selection.html)
84 changes: 84 additions & 0 deletions kotlin-samples/shinny-tokens/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import static org.gradle.api.JavaVersion.VERSION_11

plugins {
id 'org.jetbrains.kotlin.jvm'
id 'net.corda.cordapp.cordapp-configuration'
id 'org.jetbrains.kotlin.plugin.jpa'
id 'java'
id 'maven-publish'
id 'net.corda.plugins.csde'
}

allprojects {
group 'com.r3.developers.samples'
version '1.0-SNAPSHOT'

def javaVersion = VERSION_11

// Configure the CSDE
csde {
cordaClusterURL = "https://localhost:8888"
networkConfigFile = "config/static-network-config.json"
r3RootCertFile = "config/r3-ca-key.pem"
corDappCpiName = "MyCorDapp"
notaryCpiName = "NotaryServer"
cordaRpcUser = "admin"
cordaRpcPasswd ="admin"
workflowsModuleName = workflowsModule
csdeWorkspaceDir = "workspace"
notaryVersion = cordaNotaryPluginsVersion
combinedWorkerVersion = combinedWorkerJarVersion
postgresJdbcVersion = "42.4.3"
cordaDbContainerName = "CSDEpostgresql"
cordaBinDir = "${System.getProperty("user.home")}/.corda/corda5"
cordaCliBinDir = "${System.getProperty("user.home")}/.corda/cli"
}

// Declare the set of Kotlin compiler options we need to build a CorDapp.
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
allWarningsAsErrors = false

// Specify the version of Kotlin that we are that we will be developing.
languageVersion = '1.7'
// Specify the Kotlin libraries that code is compatible with
apiVersion = '1.7'
// Note that we Need to use a version of Kotlin that will be compatible with the Corda API.
// Currently that is developed in Kotlin 1.7 so picking the same version ensures compatibility with that.

// Specify the version of Java to target.
jvmTarget = javaVersion

// Needed for reflection to work correctly.
javaParameters = true

// -Xjvm-default determines how Kotlin supports default methods.
// JetBrains currently recommends developers use -Xjvm-default=all
// https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-jvm-default/
freeCompilerArgs += [
"-Xjvm-default=all"
]
}
}

repositories {
// All dependencies are held in Maven Central
mavenLocal()
mavenCentral()
}

tasks.withType(Test).configureEach {
useJUnitPlatform()
}

}

publishing {
publications {
maven(MavenPublication) {
artifactId "corda5-token-cordapp"
groupId project.group
artifact jar
}
}
}
13 changes: 13 additions & 0 deletions kotlin-samples/shinny-tokens/config/gradle-plugin-default-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB7zCCAZOgAwIBAgIEFyV7dzAMBggqhkjOPQQDAgUAMFsxCzAJBgNVBAYTAkdC
MQ8wDQYDVQQHDAZMb25kb24xDjAMBgNVBAoMBUNvcmRhMQswCQYDVQQLDAJSMzEe
MBwGA1UEAwwVQ29yZGEgRGV2IENvZGUgU2lnbmVyMB4XDTIwMDYyNTE4NTI1NFoX
DTMwMDYyMzE4NTI1NFowWzELMAkGA1UEBhMCR0IxDzANBgNVBAcTBkxvbmRvbjEO
MAwGA1UEChMFQ29yZGExCzAJBgNVBAsTAlIzMR4wHAYDVQQDExVDb3JkYSBEZXYg
Q29kZSBTaWduZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQDjSJtzQ+ldDFt
pHiqdSJebOGPZcvZbmC/PIJRsZZUF1bl3PfMqyG3EmAe0CeFAfLzPQtf2qTAnmJj
lGTkkQhxo0MwQTATBgNVHSUEDDAKBggrBgEFBQcDAzALBgNVHQ8EBAMCB4AwHQYD
VR0OBBYEFLMkL2nlYRLvgZZq7GIIqbe4df4pMAwGCCqGSM49BAMCBQADSAAwRQIh
ALB0ipx6EplT1fbUKqgc7rjH+pV1RQ4oKF+TkfjPdxnAAiArBdAI15uI70wf+xlL
zU+Rc5yMtcOY4/moZUq36r0Ilg==
-----END CERTIFICATE-----
51 changes: 51 additions & 0 deletions kotlin-samples/shinny-tokens/config/log4j2.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} %X - %msg%n"/>
</Console>

<RollingFile name="App"
fileName="logs/corda.log"
filePattern="logs/corda.%d{MM-dd-yyyy}.%i.log"
ignoreExceptions="false">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} %X - %msg%n"/>
<Policies>
<OnStartupTriggeringPolicy />
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
<DefaultRolloverStrategy>
<Delete basePath="logs/">
<IfFileName glob="logs/corda.*.log">
<IfAny>
<IfAccumulatedFileSize exceeds="500 MB" />
<IfAccumulatedFileCount exceeds="10" />
</IfAny>
</IfFileName>
<IfLastModified age="7d" />
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>
<Loggers>
<logger name="Console">
<AppenderRef ref="Console" level="info"/>
</logger>

<!-- log warn only for these 3rd party libs -->
<Logger name="com.zaxxer.hikari" level="warn" />
<Logger name="io.javalin.Javalin" level="warn" />
<Logger name="org.apache.aries.spifly" level="warn" />
<Logger name="org.apache.kafka" level="warn" />
<Logger name="org.eclipse.jetty" level="warn" />
<Logger name="org.hibernate" level="warn" />

<!-- default to warn only for OSGi logging -->
<Logger name="net.corda.osgi.framework.OSGiFrameworkWrap" level="warn" />

<root level="debug">
<AppenderRef ref="App" level="info"/>
</root>
</Loggers>
</Configuration>
32 changes: 32 additions & 0 deletions kotlin-samples/shinny-tokens/config/r3-ca-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg
RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV
UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y
ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If
xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV
ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO
DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ
jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/
CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi
EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM
fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY
uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK
chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t
9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD
ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2
SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd
+SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc
fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa
sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N
cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N
0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie
4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI
r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1
/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm
gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+
-----END CERTIFICATE-----
19 changes: 19 additions & 0 deletions kotlin-samples/shinny-tokens/config/static-network-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[
{
"x500Name" : "CN=Alice, OU=Test Dept, O=R3, L=London, C=GB",
"cpi" : "MyCorDapp"
},
{
"x500Name" : "CN=Bob, OU=Test Dept, O=R3, L=London, C=GB",
"cpi" : "MyCorDapp"
},
{
"x500Name" : "CN=Charlie, OU=Test Dept, O=R3, L=London, C=GB",
"cpi" : "MyCorDapp"
},
{
"x500Name" : "CN=NotaryRep1, OU=Test Dept, O=R3, L=London, C=GB",
"cpi" : "NotaryServer",
"serviceX500Name": "CN=NotaryService, OU=Test Dept, O=R3, L=London, C=GB"
}
]
Loading

0 comments on commit 635d4db

Please sign in to comment.