├── .ci
└── Dockerfile
├── .gitignore
├── .idea
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
└── runConfigurations
│ ├── Debug_CorDapp.xml
│ ├── Run_Contract_Tests.xml
│ ├── Run_Flow_Tests.xml
│ ├── Run_Integration_Tests.xml
│ ├── Run_Template_Client.xml
│ ├── Run_Template_Cordapp.xml
│ ├── Run_Template_Server.xml
│ └── Unit_tests.xml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── CONTRIBUTORS.md
├── Jenkinsfile
├── LICENCE
├── QUESTIONS.md
├── README.md
├── ROADMAP.md
├── SELECTION-TODO.md
├── TRADEMARK
├── build.gradle
├── config
├── dev
│ └── log4j2.xml
└── test
│ └── log4j2.xml
├── contracts
├── build.gradle
└── src
│ ├── main
│ └── kotlin
│ │ └── com
│ │ └── r3
│ │ └── corda
│ │ └── lib
│ │ └── tokens
│ │ └── contracts
│ │ ├── AbstractTokenContract.kt
│ │ ├── EvolvableTokenContract.kt
│ │ ├── FungibleTokenContract.kt
│ │ ├── NonFungibleTokenContract.kt
│ │ ├── commands
│ │ ├── EvolvableTokenTypeCommand.kt
│ │ └── TokenCommand.kt
│ │ ├── internal
│ │ └── schemas
│ │ │ ├── FungibleTokenSchema.kt
│ │ │ └── NonFungibleTokenSchema.kt
│ │ ├── schemas
│ │ └── TokenSchema.kt
│ │ ├── states
│ │ ├── AbstractToken.kt
│ │ ├── EvolvableTokenType.kt
│ │ ├── FungibleToken.kt
│ │ └── NonFungibleToken.kt
│ │ ├── types
│ │ ├── IssuedTokenType.kt
│ │ ├── TokenPointer.kt
│ │ └── TokenType.kt
│ │ └── utilities
│ │ ├── AmountUtilities.kt
│ │ ├── TokenUtilities.kt
│ │ └── TransactionUtilities.kt
│ └── test
│ ├── java
│ └── com
│ │ └── r3
│ │ └── corda
│ │ └── lib
│ │ └── tokens
│ │ └── contracts
│ │ ├── FungibleTokenJavaTests.java
│ │ └── NonFungibleTokenJavaTests.java
│ └── kotlin
│ └── com
│ └── r3
│ └── corda
│ └── lib
│ └── tokens
│ └── contracts
│ ├── CommonTokens.kt
│ ├── ContractTestCommon.kt
│ ├── EvolvableTokenTests.kt
│ ├── FungibleTokenTests.kt
│ ├── NonFungibleTokenTests.kt
│ ├── TestEvolvableTokenContract.kt
│ └── TokenPointerTests.kt
├── design
├── banking-system.png
├── chain-of-agreements.png
├── design.md
├── new-state-hierarchy.png
├── pointer.png
├── state-hierarchy.png
├── taxonomy.png
├── token-selection.md
└── token-type-fungible-token.png
├── deterministic.gradle
├── docs
├── DvPTutorial.md
├── IWantTo.md
├── InMemoryTokenSelection.md
└── OVERVIEW.md
├── freighter-tests
├── build.gradle
└── src
│ └── freighterTest
│ └── kotlin
│ └── freighter
│ └── testing
│ ├── NullHolderOnObserverTest.kt
│ ├── TokenSDKDBCompatibility.kt
│ ├── TokenSDKUpgrade46Compatibility.kt
│ └── TokenSDKUpgradeDBCompatibility.kt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── lib
├── README.txt
└── quasar.jar
├── modules
├── contracts-for-testing
│ ├── build.gradle
│ └── src
│ │ └── main
│ │ └── kotlin
│ │ └── com
│ │ └── r3
│ │ └── corda
│ │ └── lib
│ │ └── tokens
│ │ └── testing
│ │ ├── contracts
│ │ ├── DiamondGradingReportContract.kt
│ │ ├── HouseContract.kt
│ │ └── TestEvolvableTokenContract.kt
│ │ └── states
│ │ ├── DiamondGradingReport.kt
│ │ ├── House.kt
│ │ ├── Rubles.kt
│ │ └── TestEvolvableTokenType.kt
└── selection
│ ├── build.gradle
│ └── src
│ └── main
│ └── kotlin
│ └── com.r3.corda.lib.tokens.selection
│ ├── SelectionUtilities.kt
│ ├── TokenQueryBy.kt
│ ├── api
│ ├── SateSelectionConfig.kt
│ └── Selector.kt
│ ├── database
│ ├── config
│ │ └── DatabaseSelectionConfig.kt
│ └── selector
│ │ └── DatabaseTokenSelection.kt
│ └── memory
│ ├── config
│ └── InMemorySelectionConfig.kt
│ ├── internal
│ └── ExternalIdIndexUtils.kt
│ ├── selector
│ └── LocalTokenSelector.kt
│ └── services
│ ├── VaultMigratorService.kt
│ └── VaultWatcherService.kt
├── settings.gradle
├── workflows-integration-test
├── build.gradle
└── src
│ ├── integrationTest
│ └── kotlin
│ │ └── com
│ │ └── r3
│ │ └── corda
│ │ └── lib
│ │ └── tokens
│ │ └── integrationTest
│ │ └── TokenDriverTest.kt
│ └── main
│ └── kotlin
│ └── com
│ └── r3
│ └── corda
│ └── lib
│ └── tokens
│ └── integration
│ └── workflows
│ └── TestFlows.kt
└── workflows
├── build.gradle
└── src
├── main
├── kotlin
│ └── com
│ │ └── r3
│ │ └── corda
│ │ └── lib
│ │ └── tokens
│ │ ├── money
│ │ ├── DigitalCurrency.kt
│ │ ├── FiatCurrency.kt
│ │ └── Utilities.kt
│ │ └── workflows
│ │ ├── Exceptions.kt
│ │ ├── OwnerMigration.kt
│ │ ├── flows
│ │ ├── confidential
│ │ │ ├── ConfidentialTokensFlow.kt
│ │ │ └── ConfidentialTokensFlowHandler.kt
│ │ ├── evolvable
│ │ │ ├── CreateEvolvableTokensFlow.kt
│ │ │ ├── CreateEvolvableTokensFlowHandler.kt
│ │ │ ├── EvolvableTokenUtilities.kt
│ │ │ ├── UpdateEvolvableTokenFlow.kt
│ │ │ └── UpdateEvolvableTokenFlowHandler.kt
│ │ ├── issue
│ │ │ ├── ConfidentialIssueTokensFlow.kt
│ │ │ ├── ConfidentialIssueTokensFlowHandler.kt
│ │ │ ├── IssueTokensFlow.kt
│ │ │ ├── IssueTokensFlowHandler.kt
│ │ │ └── IssueTokensUtilities.kt
│ │ ├── move
│ │ │ ├── AbstractMoveTokensFlow.kt
│ │ │ ├── ConfidentialMoveFungibleTokensFlow.kt
│ │ │ ├── ConfidentialMoveNonFungibleTokensFlow.kt
│ │ │ ├── ConfidentialMoveTokensFlowHandler.kt
│ │ │ ├── MoveFungibleTokensFlow.kt
│ │ │ ├── MoveNonFungibleTokensFlow.kt
│ │ │ ├── MoveTokensFlow.kt
│ │ │ ├── MoveTokensFlowHandler.kt
│ │ │ └── MoveTokensUtilities.kt
│ │ ├── redeem
│ │ │ ├── AbstractRedeemTokensFlow.kt
│ │ │ ├── ConfidentialRedeemFungibleTokensFlow.kt
│ │ │ ├── ConfidentialRedeemFungibleTokensFlowHandler.kt
│ │ │ ├── RedeemFlowUtilities.kt
│ │ │ ├── RedeemFungibleTokensFlow.kt
│ │ │ ├── RedeemNonFungibleTokensFlow.kt
│ │ │ ├── RedeemTokensFlow.kt
│ │ │ └── RedeemTokensFlowHandler.kt
│ │ └── rpc
│ │ │ ├── EvolvableTokens.kt
│ │ │ ├── IssueTokens.kt
│ │ │ ├── MoveTokens.kt
│ │ │ └── RedeemTokens.kt
│ │ ├── internal
│ │ ├── CheckUtilities.kt
│ │ ├── README.md
│ │ ├── flows
│ │ │ ├── confidential
│ │ │ │ ├── AnonymisePartiesFlow.kt
│ │ │ │ ├── AnonymisePartiesFlowHandler.kt
│ │ │ │ └── ConfidentialIdentityUtilities.kt
│ │ │ ├── distribution
│ │ │ │ ├── DistributionUtilities.kt
│ │ │ │ ├── README.md
│ │ │ │ ├── RequestAdditionToDistributionList.kt
│ │ │ │ ├── UpdateDistributionListFlow.kt
│ │ │ │ └── UpdateDistributionListFlowHandler.kt
│ │ │ └── finality
│ │ │ │ ├── FinalityUtilities.kt
│ │ │ │ ├── ObserverAwareFinalityFlow.kt
│ │ │ │ └── ObserverAwareFinalityFlowHandler.kt
│ │ ├── schemas
│ │ │ └── DistributionRecord.kt
│ │ └── selection
│ │ │ └── NonFungibleTokenSelection.kt
│ │ ├── types
│ │ └── PartyAndAmount.kt
│ │ └── utilities
│ │ ├── FlowUtilities.kt
│ │ ├── FungibleTokenBuilder.kt
│ │ ├── NonFungibleTokenBuilder.kt
│ │ ├── NotaryUtilities.kt
│ │ ├── QueryUtilities.kt
│ │ └── TokenUtilities.kt
└── resources
│ └── migration
│ ├── distribution-record-schema-v1.changelog-master.xml
│ ├── distribution-record-schema.changelog-init.xml
│ ├── fungible-token-schema-update-1.xml
│ ├── fungible-token-schema-v1.changelog-master.xml
│ ├── fungible-token-schema.changelog-1.xml
│ ├── fungible-token-schema.changelog-init.xml
│ ├── non-fungible-token-schema-update-1.xml
│ ├── non-fungible-token-schema-v1.changelog-master.xml
│ ├── non-fungible-token-schema.changelog-init.xml
│ └── token-schema-v1.changelog-master.xml
└── test
├── java
└── com
│ └── r3
│ └── corda
│ └── lib
│ └── tokens
│ ├── money
│ └── CurrencyAccessFromJavaTest.java
│ └── workflows
│ ├── FungibleTokenBuilderTests.java
│ ├── NonFungibleTokenBuilderTests.java
│ └── utilities
│ ├── NotaryUtilitiesFromJavaTest.java
│ └── SelectionUtilitiesFromJavaTest.java
└── kotlin
└── com
└── r3
└── corda
└── lib
└── tokens
└── workflows
├── ConfigSelectionTest.kt
├── CreateEvolvableTokenTests.kt
├── DatabaseTokenSelectionTests.kt
├── DiamondWithTokenScenarioTests.kt
├── Examples.kt
├── FlowHelpers.kt
├── InMemorySelectionTest.kt
├── IssueTokensTest.kt
├── JITMockNetworkTests.kt
├── LedgerTestWithPersistence.kt
├── MockNetworkTest.kt
├── MoveTokensTest.kt
├── RedeemTokenTest.kt
├── RedeemTokenTestsFlow.kt
├── TestHelpers.kt
├── TokenFlowTests.kt
├── TokenQueryTests.kt
├── TokenSelectionTestFlows.kt
├── TokenSelectionWithInFlowTest.kt
├── TransactionUtilityTests.kt
├── UpdateEvolvableTokenTests.kt
├── VaultWatcherServiceTest.kt
└── factories
└── TestEvolvableTokenTypeFactory.kt
/.ci/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM azul/zulu-openjdk:8
2 | RUN apt-get update && apt-get install -y curl apt-transport-https \
3 | ca-certificates \
4 | curl \
5 | gnupg2 \
6 | software-properties-common \
7 | wget
8 | RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
9 | RUN add-apt-repository \
10 | "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
11 | $(lsb_release -cs) \
12 | stable"
13 | RUN apt-get update && apt-get install -y docker-ce-cli
14 |
15 | RUN SUFFIX= && DEBIAN_ARCH="$(dpkg --print-architecture)" && DEST=/tmp/snyk \
16 | && if [ "${DEBIAN_ARCH}" != amd64 ]; then SUFFIX="-${DEBIAN_ARCH}"; fi \
17 | && curl -o "${DEST}" -#L "https://github.com/snyk/cli/releases/latest/download/snyk-linux${SUFFIX}" \
18 | && for item in snyk snyk-linux; do install -v -T --mode=0755 "${DEST}" "/usr/local/bin/${item}"; done \
19 | && rm "${DEST}"
20 |
21 | RUN DEBIAN_ARCH="$(dpkg --print-architecture)" && DEST=/tmp/snyk-to-html \
22 | && if [ "${DEBIAN_ARCH}" != amd64 ]; then echo "There is no binary for ${DEBIAN_ARCH} architecture, skipping installation"; exit 0; fi \
23 | && curl -o "${DEST}" -#L "https://github.com/snyk/snyk-to-html/releases/latest/download/snyk-to-html-linux" \
24 | && for item in snyk-to-html snyk-to-html-linux; do install -v -T --mode=0755 "${DEST}" "/usr/local/bin/${item}"; done \
25 | && rm "${DEST}"
26 |
27 | RUN DEBIAN_ARCH="$(dpkg --print-architecture)" && DEST=/tmp/snyk-delta \
28 | && if [ "${DEBIAN_ARCH}" != amd64 ]; then echo "There is no binary for ${DEBIAN_ARCH} architecture, skipping installation"; exit 0; fi \
29 | && curl -o "${DEST}" "https://github.com/snyk-tech-services/snyk-delta/releases/latest/download/snyk-delta-linux" \
30 | && for item in snyk-delta snyk-delta-linux; do install -v -T --mode=0755 "${DEST}" "/usr/local/bin/${item}"; done \
31 | && rm "${DEST}"
32 |
33 | ARG USER="stresstester"
34 | RUN useradd -m ${USER}
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Eclipse, ctags, Mac metadata, log files
2 | .classpath
3 | .project
4 | .settings
5 | tags
6 | .DS_Store
7 | *.log
8 | *.log.gz
9 | *.orig
10 |
11 | .gradle
12 |
13 | # General build files
14 | **/build/*
15 | !docs/build/*
16 |
17 | lib/dokka.jar
18 |
19 | ### JetBrains template
20 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
21 |
22 | *.iml
23 |
24 | ## Directory-based project format:
25 | #.idea
26 |
27 | # if you remove the above rule, at least ignore the following:
28 |
29 | # Specific files to avoid churn
30 | .idea/*.xml
31 | .idea/copyright
32 | .idea/jsLibraryMappings.xml
33 |
34 | # User-specific stuff:
35 | .idea/tasks.xml
36 | .idea/dictionaries
37 |
38 | # Sensitive or high-churn files:
39 | .idea/dataSources.ids
40 | .idea/dataSources.xml
41 | .idea/sqlDataSources.xml
42 | .idea/dynamic.xml
43 | .idea/uiDesigner.xml
44 |
45 | # Gradle:
46 | .idea/libraries
47 |
48 | # Mongo Explorer plugin:
49 | .idea/mongoSettings.xml
50 |
51 | ## File-based project format:
52 | *.ipr
53 | *.iws
54 |
55 | ## Plugin-specific files:
56 |
57 | # IntelliJ
58 | **/out/*
59 |
60 | # mpeltonen/sbt-idea plugin
61 | .idea_modules/
62 |
63 | # JIRA plugin
64 | atlassian-ide-plugin.xml
65 |
66 | # Crashlytics plugin (for Android Studio and IntelliJ)
67 | com_crashlytics_export_strings.xml
68 | crashlytics.properties
69 | crashlytics-build.properties
70 |
71 | # docs related
72 | docs/virtualenv/
73 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Debug_CorDapp.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Run_Contract_Tests.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
16 |
17 |
18 | true
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Run_Flow_Tests.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | true
19 |
20 |
21 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Run_Integration_Tests.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | true
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Run_Template_Client.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Run_Template_Cordapp.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Run_Template_Server.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Unit_tests.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Token SDK
2 |
3 | Token SDK is an open-source project and contributions are welcome!
4 |
5 | To find out how to contribute, please see our [contributing docs](https://docs.corda.net/head/contributing-index.html).
6 |
--------------------------------------------------------------------------------
/CONTRIBUTORS.md:
--------------------------------------------------------------------------------
1 | # List of Contributors
2 |
3 | We'd like to thank the following people for contributing to Token SDK, either by contributing to the design of Token SDK during design reviews since the initial design and code has been open-sourced, or by contributing code via pull requests.
4 |
5 | Some people have moved to a different organisation since their contribution. Please forgive any omissions, and create a pull request, or email , if you wish to see changes to this list.
6 |
7 | * Roger Willis (R3) https://github.com/roger3cev
8 | * Clinton Alexander (R3) https://github.com/Clintonio
9 | * Joel Dudley (R3) https://github.com/joeldudleyr3
10 | * Ian Lloyd (?) https://github.com/illoyd
11 | * Tudor Malene (R3) https://github.com/tudor-malene
12 | * Katarzyna Streich (R3) https://github.com/kasiastreich
13 | * Jose Coll (R3) https://github.com/josecoll
14 | * Ross Nicoll (Dapper Labs) https://github.com/rnicoll
15 | * Andrius Dagys (R3) https://github.com/adagys
16 | * Mike Hearn (R3) https://github.com/mikehearn
17 | * Patrick Kuo (R3) https://github.com/patrickkuo
18 | * Richard Green (Blocksure) https://github.com/ragmondo
19 | * Chris Rankin (R3) https://github.com/chrisr3
20 | * Will Hester (R3) https://github.com/willhr3
21 | * Alex Koller (R3) https://github.com/Alex-Koller-R3
22 | * Rick Parker (R3) https://github.com/rick-r3
23 | * Maksymilian Pawlak (B3i) https://github.com/m4ksio
24 | * Shams Asari (R3) https://github.com/shamsasari
25 | * Alex Goldvarg (Giant Machines) https://github.com/agoldvarg
26 | * David Lee (BCS Consulting) https://github.com/davidleeuk
27 | * Stefano Franz (R3) https://github.com/roastario
28 | * Dominic Fox (R3) https://github.com/r3domfox
29 | * Katelyn Baker (R3) https://github.com/fenryka
30 | * Cais Manai (R3) https://github.com/CaisR3
31 | * Andrzej Cichocki (R3) https://github.com/andr3ej
32 | * David Wray (R3) https://github.com/dwray
33 | * Anthony Keenan (R3) https://github.com/anthonykeenan
34 | * unknown (?) https://github.com/cxyzhang0
35 | * Konstantinos Chalkias (Facebook) https://github.com/kchalkias
36 | * Chris Burlinchon (R3) https://github.com/cburlinchon
37 | * Tommas (?) https://github.com/tb-pq
38 | * Adam Furgal (R3) https://github.com/afurgal
39 | * Ben Wyeth (lab577) https://github.com/nimmaj
40 | * Sergey Udovenko (SDX)
41 | * Philipp Küng (SDX) https://github.com/philippkueng
42 | * Jonathan Lodge (SDX)
43 | * Janis Okekss (Accenture)
44 | * David Nicol (R3)
45 | * Antony Lewis (R3)
46 | * Richard Brown (R3)
47 |
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | Copyright 2016, R3 Limited.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
--------------------------------------------------------------------------------
/ROADMAP.md:
--------------------------------------------------------------------------------
1 | # Token SDK Roadmap
2 |
3 | ## V2
4 |
5 | * Improved Java API
6 | * Add better query utilities for tokens and held tokens.
7 | * Performance updates for in memory token selection.
8 | * Use of in memory token selection as the primary way to select tokens
9 |
10 | ## vNext
11 |
12 | * Addition of a new JAR which contains flows used by token issuers only.
13 | * Addition of an off-ledger mapping of how many tokens of each type have been issued. The mapping will be updated via the vault observable when tokens are issued and redeemed.
14 | * Addition of a flow which performs "chain-snipping". This flow allows a node operator to select some tokens by issuer and redeem them with the issuer, in return for some fresh tokens with no back-chain.
15 | * Addition of flows to provide issuer-whitelisting. This is useful when an issuer only wants specific parties to hold it's own issued tokens.
16 | * Addition of the "client" side of the "chain-snipping" flow.
17 | * Addition of a service to gather statistics regarding the spending behaviour of the node. This can be used to optimise the size and number of states in the vault.
18 | * Addition of flows and utilities which allow nodes to whitelist issuers. Tokens issued by issuers which are not in the whitelist will not be accepted.
19 | * Updates to the token contracts to handle issuer-whitelisting.
20 | * Publish standards
21 | * Vault grooming - to optimise token sizes for spending, e.g. bucket coins into appropriate denominations. this should be merged into regular spending workflows to reduce the amount of transactions required
22 | * Begin to start defining abstract types for commonly used evolvable tokens, e.g. equities and bonds
23 | * Add support for "wallets"
24 | * Add support for keys generated out of process
25 | * Add flows to handle typical abstractions that have been identified E.g. atomic swaps, repos, lending...
26 | * Add more support and tooling for issuers. Merge in the work done on the cash-issuer into the Issuer workflows module. The issuer module will contain utilities for issuers such as keeping track of issued tokens and managing off-ledger records.
27 | * Zero knowledge proofs for amounts and potentially public keys
28 | * Integrate the ISDA CDM to the token SDK
29 |
--------------------------------------------------------------------------------
/SELECTION-TODO.md:
--------------------------------------------------------------------------------
1 | In 1.1 version of `token-sdk` we introduced in memory token selection and unified it with the database one.
2 | However, the work isn't fully finished yet, because at unification step we face the problem of difference in query API.
3 | It could be easily solved, but to do so we have to break the API (which I recommend doing in 2.0 release, we shouldn't do that in minor releases).
4 | Why? In initial days pre 1.0 we introduced vault `queryCriteria` parameter on all token-sdk flows (feature requested by one of our customers).
5 | In hindsight, it was an omission on API, as after splitting `Selector` interface and database and in memory token selection
6 | (see `selection` module) only point that those two don't align is querying. We considered some of the alternative solutions,
7 | there are 3 major ways of solving the problem:
8 | 1. Restricting the query for tokens to certain operations like querying by issuer, external id, public keys, notaries and
9 | then have filtering predicate (this is for now default internal implementation for selection, although, to avoid API break
10 | we left `queryCriteria` on `TokenQueryBy`, which should be removed when 2.0 is released). Using that restricted set of queries
11 | it is possible to translate easily between token selection modes. Moreover it is sufficient for token usage.
12 | 2. Having sealed class for criteria and subclasses that correspond to vault and in memory selections. This solution has some
13 | trade-off too, there is a danger of reinventing the vault query criteria api (when trying to be the most generic with logical operations).
14 | 3. Having translation between VaultQueryCriteria and in memory selection, but this is infeasible, because you have to support arbitrary queries,
15 | which is excessive considering our usage, there is lots of place for errors and in general it would be rather experimental.
16 |
17 | We went with simple solution number 1. selection is for now internal and not part of the API. It is possible to easily switch
18 | choice between the modes in flows (based on cordapp config options) - it's just single line of code, but it requires changes to
19 | query API in flows.
20 |
21 | We need that unification anyway, because the plan is to split selection from token-sdk and make it token agnostic, so it
22 | can be used easily from other cordapps - something for the future releases. Fundamental work is laid down already.
23 |
24 | So here is TODO list for what should be done to finish this properly:
25 |
26 | 1. Break the API on move, redeem flow family and on accompaning utilities functions in 2.0 release, old versions could be
27 | deprecated as they always fallback to database selection.
28 | 2. Introduce generic query parameter 1 or 2, if `TokenQueryBy` approach is kept then, remove query criteria, add translation
29 | to query criteria using functions from `QueryUtilities`, they already cover most common use cases.
30 | 3. Switch on the choice between selection modes in move and redeem utilities - here you have choice of passing selection method,
31 | or using config mechanism.
32 | 4. In future, split our selection into separate jar, unify the types so it's not dependent on `token-sdk`, tricky bit: states indexing
33 | in in memory token selection. `VaultWatcherService` is corda service, which means it takes `appServiceHub` only on the construction,
34 | the rest has to be passed by config which is already implemented, apart from state type bit.
35 | See: `VaultWatcherService.getObservableFromAppServiceHub` that constructs `ownerProvider` and observable that listens for
36 | certain `contractStateType`s.
37 |
--------------------------------------------------------------------------------
/TRADEMARK:
--------------------------------------------------------------------------------
1 | Corda and the Corda logo are trademarks of R3CEV LLC and its affiliates. All rights reserved.
2 |
3 | For R3CEV LLC's trademark and logo usage information, please consult our Trademark Usage Policy at
4 | https://www.r3.com/trademark-policy/.
5 |
--------------------------------------------------------------------------------
/config/dev/log4j2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | logs
6 | node-${hostName}
7 | ${log-path}/archive
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | %highlight{%level{length=1} %d{HH:mm:ss} %T %c{1}.%M - %msg%n}{INFO=white,WARN=red,FATAL=bright red blink}
17 | >
18 |
19 |
20 |
21 |
23 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/config/test/log4j2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %highlight{[%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n}{INFO=white,WARN=red,FATAL=bright red
8 | blink}
9 | >
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/contracts/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'kotlin-jpa'
2 | apply plugin: 'net.corda.plugins.cordapp'
3 |
4 | description 'token-sdk contracts'
5 |
6 | if (!(corda_release_version in ['4.1'])) {
7 | apply from: "${rootProject.projectDir}/deterministic.gradle"
8 | }
9 |
10 | sourceSets {
11 | main {
12 | resources {
13 | srcDir rootProject.file("config/dev")
14 | }
15 | }
16 | test {
17 | resources {
18 | srcDir rootProject.file("config/test")
19 | }
20 | }
21 | }
22 |
23 | dependencies {
24 | // Kotlin.
25 | compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
26 |
27 | // Corda dependencies.
28 | cordaCompile("$corda_release_group:corda-core:$corda_release_version") {
29 | changing = true
30 | }
31 |
32 | // Logging.
33 | testCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
34 |
35 | // Testing.
36 | testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
37 | testCompile "junit:junit:$junit_version"
38 | testCompile "$corda_release_group:corda-node-driver:$corda_release_version"
39 | testCompile project(":modules:contracts-for-testing")
40 | }
41 |
42 | cordapp {
43 | targetPlatformVersion 6
44 | minimumPlatformVersion 6
45 | contract {
46 | name "Token SDK Contracts"
47 | vendor "R3"
48 | licence "Apache 2"
49 | versionId 2
50 | }
51 | signing {
52 | enabled false
53 | }
54 | }
55 |
56 | jar {
57 | baseName "tokens-contracts"
58 | }
--------------------------------------------------------------------------------
/contracts/src/main/kotlin/com/r3/corda/lib/tokens/contracts/commands/EvolvableTokenTypeCommand.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.contracts.commands
2 |
3 | import com.r3.corda.lib.tokens.contracts.states.EvolvableTokenType
4 | import net.corda.core.contracts.CommandData
5 | import net.corda.core.contracts.TypeOnlyCommandData
6 |
7 | /**
8 | * A standard set of commands for creating and updating [EvolvableTokenType]s. Additional commands can be added
9 | * which implement [EvolvableTokenTypeCommand]. For example, a stock token will require a "stock split" command,
10 | * which would implement the [EvolvableTokenTypeCommand] interface.
11 | *
12 | * Note, that for the time being, [EvolvableTokenType]s cannot be removed from the ledger. Doing so would break
13 | * the evolvable token model. Given that [EvolvableTokenType]s are not storage hungry, this is an acceptable
14 | * trade-off - they can just persist on the ledger even if they are not required any more.
15 | */
16 | interface EvolvableTokenTypeCommand : CommandData
17 |
18 | /** Used when creating new [EvolvableTokenType]s. */
19 | class Create : EvolvableTokenTypeCommand, TypeOnlyCommandData()
20 |
21 | /** Used when updating existing [EvolvableTokenType]s. */
22 | class Update : EvolvableTokenTypeCommand, TypeOnlyCommandData()
--------------------------------------------------------------------------------
/contracts/src/main/kotlin/com/r3/corda/lib/tokens/contracts/commands/TokenCommand.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.contracts.commands
2 |
3 | import com.r3.corda.lib.tokens.contracts.states.FungibleToken
4 | import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken
5 | import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType
6 | import com.r3.corda.lib.tokens.contracts.types.TokenType
7 | import net.corda.core.contracts.CommandData
8 |
9 |
10 | /**
11 | * [TokenCommand]s are linked to groups of input and output tokens by the [IssuedTokenType]. This needs to be done
12 | * because if a transaction contains more than one type of token, we need to handle inputs and outputs grouped by token
13 | * type. Furthermore, we need to distinguish between the same token issued by two different issuers as the same token
14 | * issued by different issuers is not fungible, so one cannot add or subtract them. This is why [IssuedTokenType] is
15 | * used. The [IssuedTokenType] is also included in the [TokenType] so each command can be linked to a group. The
16 | * [AbstractTokenContract] doesn't allow a group of tokens without an associated [Command].
17 | *
18 | * @property token the group of [IssuedTokenType]s this command should be tied to.
19 | */
20 | abstract class TokenCommand(open val token: IssuedTokenType, internal val inputIndicies: List = listOf(), internal val outputIndicies: List = listOf()) : CommandData {
21 | fun inputIndicies(): List {
22 | return inputIndicies.sortedBy { it }
23 | }
24 |
25 | fun outputIndicies(): List {
26 | return outputIndicies.sortedBy { it }
27 | }
28 |
29 | override fun equals(other: Any?): Boolean {
30 | if (this === other) return true
31 | if (javaClass != other?.javaClass) return false
32 |
33 | other as TokenCommand
34 |
35 | if (token != other.token) return false
36 | if (inputIndicies != other.inputIndicies) return false
37 | if (outputIndicies != other.outputIndicies) return false
38 |
39 | return true
40 | }
41 |
42 | override fun hashCode(): Int {
43 | var result = token.hashCode()
44 | result = 31 * result + inputIndicies.hashCode()
45 | result = 31 * result + outputIndicies.hashCode()
46 | return result
47 | }
48 |
49 | override fun toString(): String {
50 | return "${this.javaClass.name}(token=$token, inputIndicies=$inputIndicies, outputIndicies=$outputIndicies)"
51 | }
52 |
53 | }
54 |
55 | /**
56 | * Used when issuing [FungibleToken]s or [NonFungibleToken]s.
57 | *
58 | * @property token the group of [IssuedTokenType]s this command should be tied to.
59 | * @property outputs the output state indices this command applies to.
60 | */
61 | class IssueTokenCommand(override val token: IssuedTokenType, val outputs: List = listOf()) : TokenCommand(outputIndicies = outputs, token = token)
62 |
63 | /**
64 | * Used when moving [FungibleToken]s or [NonFungibleToken]s.
65 | *
66 | * @property token the group of [IssuedTokenType]s this command should be tied to.
67 | * @property inputs the input state indices this command applies to.
68 | * @property outputs the output state indices this command applies to.
69 | */
70 | class MoveTokenCommand(override val token: IssuedTokenType, val inputs: List = listOf(), val outputs: List = listOf()) : TokenCommand(inputIndicies = inputs, outputIndicies = outputs, token = token)
71 |
72 | /**
73 | * Used when redeeming [FungibleToken]s or [NonFungibleToken]s.
74 | *
75 | * @property token the group of [IssuedTokenType]s this command should be tied to.
76 | * @property inputs the input state indices this command applies to.
77 | * @property outputs the output state indices this command applies to.
78 | */
79 | class RedeemTokenCommand(override val token: IssuedTokenType, val inputs: List = listOf(), val outputs: List = listOf()) : TokenCommand(inputIndicies = inputs, outputIndicies = outputs, token = token)
--------------------------------------------------------------------------------
/contracts/src/main/kotlin/com/r3/corda/lib/tokens/contracts/internal/schemas/FungibleTokenSchema.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.contracts.internal.schemas
2 |
3 | import net.corda.core.identity.AbstractParty
4 | import net.corda.core.identity.Party
5 | import net.corda.core.schemas.MappedSchema
6 | import net.corda.core.schemas.PersistentState
7 | import java.security.PublicKey
8 | import javax.persistence.*
9 |
10 | object FungibleTokenSchema
11 |
12 | object FungibleTokenSchemaV1 : MappedSchema(
13 | schemaFamily = FungibleTokenSchema.javaClass,
14 | version = 1,
15 | mappedTypes = listOf(PersistentFungibleToken::class.java)
16 | )
17 |
18 | @Entity
19 | @Table(name = "fungible_token", indexes = [
20 | Index(name = "amount_idx", columnList = "amount"),
21 | Index(name = "held_token_amount_idx", columnList = "token_class, token_identifier"),
22 | Index(name = "holding_key_idx", columnList = "holding_key")
23 | ])
24 | class PersistentFungibleToken(
25 | @Column(name = "issuer", nullable = false)
26 | var issuer: Party,
27 |
28 | @Column(name = "holder")
29 | var holder: AbstractParty?,
30 |
31 | @Column(name = "amount", nullable = false)
32 | var amount: Long,
33 |
34 | // The fully qualified class name of the class which implements the token tokenType.
35 | // This is either a fixed token or a evolvable token.
36 | @Column(name = "token_class", nullable = false)
37 | @Convert(converter = TokenClassConverter::class)
38 | var tokenClass: Class<*>,
39 |
40 | // This can either be a symbol or a linearID depending on whether the token is evolvable or fixed.
41 | // Not all tokens will have identifiers if there is only one instance for a token class, for example.
42 | // It is expected that the combination of token_class and token_symbol will be enough to identity a unique
43 | // token.
44 | @Column(name = "token_identifier", nullable = true)
45 | var tokenIdentifier: String,
46 |
47 | @Column(name = "holding_key", nullable = true)
48 | val owningKeyHash: String?
49 | ) : PersistentState()
50 |
--------------------------------------------------------------------------------
/contracts/src/main/kotlin/com/r3/corda/lib/tokens/contracts/internal/schemas/NonFungibleTokenSchema.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.contracts.internal.schemas
2 |
3 | import net.corda.core.identity.AbstractParty
4 | import net.corda.core.identity.Party
5 | import net.corda.core.schemas.MappedSchema
6 | import net.corda.core.schemas.PersistentState
7 | import javax.persistence.AttributeConverter
8 | import javax.persistence.Column
9 | import javax.persistence.Convert
10 | import javax.persistence.Entity
11 | import javax.persistence.Index
12 | import javax.persistence.Table
13 |
14 | object NonFungibleTokenSchema
15 |
16 | object NonFungibleTokenSchemaV1 : MappedSchema(
17 | schemaFamily = NonFungibleTokenSchema.javaClass,
18 | version = 1,
19 | mappedTypes = listOf(PersistentNonFungibleToken::class.java)
20 | )
21 |
22 | @Entity
23 | @Table(name = "non_fungible_token", indexes = [
24 | Index(name = "held_token_idx", columnList = "token_class, token_identifier")
25 | ])
26 | class PersistentNonFungibleToken(
27 | @Column(name = "issuer", nullable = false)
28 | var issuer: Party,
29 |
30 | @Column(name = "holder")
31 | var holder: AbstractParty?,
32 |
33 | // The fully qualified class name of the class which implements the token tokenType.
34 | // This is either a fixed token or a evolvable token.
35 | @Column(name = "token_class", nullable = false)
36 | @Convert(converter = TokenClassConverter::class)
37 | var tokenClass: Class<*>,
38 |
39 | // This can either be a symbol or a linearID depending on whether the token is evolvable or fixed.
40 | // Not all tokens will have identifiers if there is only one instance for a token class, for example.
41 | // It is expected that the combination of token_class and token_symbol will be enough to identity a unique
42 | // token.
43 | @Column(name = "token_identifier", nullable = true)
44 | var tokenIdentifier: String
45 |
46 | ) : PersistentState()
47 |
48 | class TokenClassConverter : AttributeConverter, String> {
49 | override fun convertToDatabaseColumn(attribute: Class<*>): String {
50 | return attribute.name
51 | }
52 |
53 | override fun convertToEntityAttribute(dbData: String): Class<*> {
54 | return Class.forName(dbData)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/contracts/src/main/kotlin/com/r3/corda/lib/tokens/contracts/schemas/TokenSchema.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.contracts.schemas
2 |
3 | import net.corda.core.schemas.MappedSchema
4 |
5 | /**
6 | * Here, schemas can be added for commonly used [EvolvableTokenType]s.
7 | */
8 | object TokenSchema
9 |
10 | object TokenSchemaV1 : MappedSchema(
11 | schemaFamily = TokenSchema.javaClass,
12 | version = 1,
13 | mappedTypes = listOf()
14 | )
15 |
--------------------------------------------------------------------------------
/contracts/src/main/kotlin/com/r3/corda/lib/tokens/contracts/states/AbstractToken.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.contracts.states
2 |
3 | import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType
4 | import com.r3.corda.lib.tokens.contracts.types.TokenType
5 | import net.corda.core.contracts.ContractState
6 | import net.corda.core.crypto.SecureHash
7 | import net.corda.core.identity.AbstractParty
8 | import net.corda.core.identity.Party
9 |
10 | /** Contains common token properties and functionality. */
11 | interface AbstractToken : ContractState {
12 | /** The [AbstractParty] which is currently holding (some amount of) tokens. */
13 | val holder: AbstractParty
14 |
15 | /**
16 | * The default participant is the current [holder]. However, this can be overridden if required. The standard
17 | * [FungibleToken] and [NonFungibleToken] states assume that the [holder] is the only participant but they can be
18 | * sub-classed so an observers list or "CC" list can be added.
19 | *
20 | * It is likely that this approach will need to be revisited at the Corda core level, at some point in the near
21 | * future, as there are some issues with how the participants list interacts with other Corda features, for example
22 | * notary change transactions and contract upgrade transactions.
23 | */
24 | override val participants: List get() = listOf(holder)
25 |
26 | /** The [TokenType] this [AbstractToken] is in respect of. */
27 | val tokenType: TokenType get() = issuedTokenType.tokenType
28 |
29 | /** The [IssuedTokenType]. */
30 | val issuedTokenType: IssuedTokenType
31 |
32 | /** The issuer [Party]. */
33 | val issuer: Party get() = issuedTokenType.issuer
34 |
35 | /** For creating a copy of an existing [AbstractToken] with a new holder. */
36 | fun withNewHolder(newHolder: AbstractParty): AbstractToken
37 |
38 | /** The hash of a CorDapp JAR which implements the [TokenType] specified by the type parameter [T]. */
39 | val tokenTypeJarHash: SecureHash?
40 | }
--------------------------------------------------------------------------------
/contracts/src/main/kotlin/com/r3/corda/lib/tokens/contracts/states/EvolvableTokenType.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.contracts.states
2 |
3 | import com.r3.corda.lib.tokens.contracts.types.TokenPointer
4 | import com.r3.corda.lib.tokens.contracts.types.TokenType
5 | import net.corda.core.contracts.LinearPointer
6 | import net.corda.core.contracts.LinearState
7 | import net.corda.core.identity.AbstractParty
8 | import net.corda.core.identity.Party
9 |
10 | /**
11 | * [EvolvableTokenType]s are for storing token reference data that we expect to change over time.
12 | *
13 | * [EvolvableTokenType]s _are_ state objects because the expectation is that they will evolve over time. Of course
14 | * in-lining a [LinearState] directly into the [NonFungibleToken] or [FungibleToken] state doesn't make much sense, as
15 | * you would have to perform a state update to change the token type. It makes more sense to include a pointer to the
16 | * token type instead. That's what [TokenPointer] is for. This way, the token can evolve independently to which party
17 | * currently owns (some amount) of the token. Because the [EvolvableTokenType] is not inlined into the
18 | * [NonFungibleToken] or [FungibleToken] state it does not sub-class [TokenType].
19 | */
20 | abstract class EvolvableTokenType : LinearState {
21 | /**
22 | * The [Party]s which create and maintain this token [EvolvableTokenType]. It probably _is_ the issuer of the token
23 | * but may not necessarily be. For example, a reference data maintainer may create an [EvolvableTokenType] for
24 | * some stock, keep all the details up-to-date, and distribute the updates. This [EvolvableTokenType], can
25 | * then be used by many issuers to create [FungibleToken]s (depository receipts) for the stock in question. Also
26 | * the actual stock issuer (if they had a Corda node on the network) could use the same stock token to issue ledger
27 | * native stock.
28 | */
29 | abstract val maintainers: List
30 |
31 | /** Defaults to the maintainer but can be overridden if necessary. */
32 | override val participants: List get() = maintainers
33 |
34 | /**
35 | * The number of fractional digits allowable for this token type. Specifying "0" will only allow integer amounts of
36 | * the token type. Specifying "2", allows two decimal places, like most fiat currencies, and so on...
37 | */
38 | abstract val fractionDigits: Int
39 |
40 | /** For obtaining a pointer to this [EvolveableTokenType]. */
41 | inline fun toPointer(): TokenPointer {
42 | val linearPointer = LinearPointer(linearId, T::class.java)
43 | return TokenPointer(linearPointer, fractionDigits)
44 | }
45 |
46 | /**
47 | * For obtaining a pointer to this [EvolveableTokenType].
48 | *
49 | * @param tokenTypeClass the [Class] of the [EvolvableTokenType] being pointed to.
50 | */
51 | fun toPointer(tokenTypeClass: Class): TokenPointer {
52 | val linearPointer = LinearPointer(linearId, tokenTypeClass)
53 | return TokenPointer(linearPointer, fractionDigits)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/contracts/src/main/kotlin/com/r3/corda/lib/tokens/contracts/types/IssuedTokenType.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.contracts.types
2 |
3 | import net.corda.core.contracts.Amount
4 | import net.corda.core.identity.Party
5 | import net.corda.core.serialization.CordaSerializable
6 | import java.math.BigDecimal
7 |
8 | /**
9 | * A type to wrap a [TokenType] with an issuing [Party]. The [TokenType] might be a ledger native asset such as an
10 | * equity which is issued directly on to the ledger, in which case the issuing [Party] IS the securities issuer. In
11 | * other cases, the [TokenType] would represent a depository receipt. In this case the issuing [Party] would be the
12 | * custodian or securities firm which has issued the [TokenType] on the ledger and holds the underlying security as an
13 | * asset on their balance sheet. The [Amount] of a [IssuedTokenType] they had issued would be the corresponding
14 | * liability. The issuer of the underlying security held in custody would be implied via some of information contained
15 | * within the token type state. E.g. a stock symbol or ISIN. Note: Possible name confusion with corda core
16 | * "net.corda.corda.contracts.Issued" which is not used by the Token SDK.
17 | *
18 | * @property issuer the [Party] which has issued (some amount of) this [TokenType] on ledger. Note that, in the case of
19 | * the [TokenType] being a depositary receipt, the issuer is NOT the party with the ultimate liability, instead it is
20 | * always the party which issued the [TokenType] on ledger.
21 | * @property tokenType the [TokenType] to be associated with an issuing [Party].
22 | */
23 | @CordaSerializable
24 | data class IssuedTokenType(val issuer: Party, val tokenType: TokenType) : TokenType(tokenType.tokenIdentifier, tokenType.fractionDigits) {
25 |
26 |
27 | /**
28 | * This is required by [Amount] to determine the default fraction digits when adding or subtracting amounts of
29 | * [IssuedTokenType].
30 | */
31 | override val displayTokenSize: BigDecimal get() = tokenType.displayTokenSize
32 |
33 | override fun toString(): String = "$tokenType issued by ${issuer.name.organisation}"
34 | }
--------------------------------------------------------------------------------
/contracts/src/main/kotlin/com/r3/corda/lib/tokens/contracts/types/TokenPointer.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.contracts.types
2 |
3 | import com.r3.corda.lib.tokens.contracts.states.EvolvableTokenType
4 | import com.r3.corda.lib.tokens.contracts.states.FungibleToken
5 | import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken
6 | import net.corda.core.contracts.LinearPointer
7 |
8 | /**
9 | * To harness the power of [EvolvableTokenType]s, they cannot be directly embedded in [NonFungibleToken]s or
10 | * [FungibleToken]s. Instead, a [TokenPointer] is embedded. The pointer can be resolved inside the verify function to
11 | * obtain the data within the [EvolvableTokenType]. This way, the token token type can evolve independently from the
12 | * question of which [Party] owns some issued amount of it, as the data is held in a separate state object.
13 | *
14 | * @property pointer a [LinearPointer] which points to an [EvolvableTokenType].
15 | * @property displayTokenSize required for adding or subtracting [Amount]s of [TokenPointer] (which are acting as a
16 | * proxy for an [EvolvableTokenType]).
17 | * @param T the type of [EvolvableTokenType] which is being pointed to by this [TokenPointer].
18 | */
19 | class TokenPointer(
20 | val pointer: LinearPointer,
21 | fractionDigits: Int
22 | ) : TokenType(pointer.pointer.id.toString(), fractionDigits) {
23 | /**
24 | * The fully qualified class name for the [EvolvableTokenType] being pointed to.
25 | */
26 | override val tokenClass: Class<*> get() = pointer.type
27 |
28 | override fun toString(): String = "TokenPointer($tokenClass, $tokenIdentifier)"
29 | }
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/contracts/src/main/kotlin/com/r3/corda/lib/tokens/contracts/utilities/TokenUtilities.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.contracts.utilities
2 |
3 | import com.r3.corda.lib.tokens.contracts.states.AbstractToken
4 | import com.r3.corda.lib.tokens.contracts.states.EvolvableTokenType
5 | import com.r3.corda.lib.tokens.contracts.states.FungibleToken
6 | import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType
7 | import net.corda.core.contracts.Amount
8 | import net.corda.core.contracts.TransactionState
9 | import net.corda.core.crypto.toStringShort
10 | import net.corda.core.identity.AbstractParty
11 | import net.corda.core.identity.AnonymousParty
12 | import net.corda.core.identity.Party
13 |
14 | class TokenUtilities
15 |
16 | // ------------------------------------------------------
17 | // Creates a tokens from (amounts of) issued token types.
18 | // ------------------------------------------------------
19 |
20 | /**
21 | * Creates a [FungibleToken] from an an amount of [IssuedTokenType].
22 | * E.g. Amount> -> FungibleToken.
23 | */
24 | infix fun Amount.heldBy(owner: AbstractParty): FungibleToken = _heldBy(owner)
25 |
26 | internal infix fun Amount._heldBy(owner: AbstractParty): FungibleToken {
27 | return FungibleToken(this, owner)
28 | }
29 |
30 | // --------------------------
31 | // Add a a notary to a token.
32 | // --------------------------
33 |
34 | /** Adds a notary [Party] to an [AbstractToken], by wrapping it in a [TransactionState]. */
35 | infix fun T.withNotary(notary: Party): TransactionState = _withNotary(notary)
36 |
37 | internal infix fun T._withNotary(notary: Party): TransactionState {
38 | return TransactionState(data = this, notary = notary)
39 | }
40 |
41 | /** Adds a notary [Party] to an [EvolvableTokenType], by wrapping it in a [TransactionState]. */
42 | infix fun T.withNotary(notary: Party): TransactionState = _withNotary(notary)
43 |
44 | internal infix fun T._withNotary(notary: Party): TransactionState {
45 | return TransactionState(data = this, notary = notary)
46 | }
47 |
48 | /**
49 | * Converts [AbstractToken.holder] into a more friendly string. It uses only the x500 organisation for [Party] objects
50 | * and shortens the public key for [AnonymousParty]s to the first 16 characters.
51 | */
52 | val AbstractToken.holderString: String
53 | get() =
54 | (holder as? Party)?.name?.organisation ?: holder.owningKey.toStringShort().substring(0, 16)
55 |
56 | infix fun T.withNewHolder(newHolder: AbstractParty) = withNewHolder(newHolder)
--------------------------------------------------------------------------------
/contracts/src/test/java/com/r3/corda/lib/tokens/contracts/FungibleTokenJavaTests.java:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.contracts;
2 |
3 | import com.r3.corda.lib.tokens.contracts.states.FungibleToken;
4 | import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType;
5 | import org.junit.Test;
6 |
7 | import static com.r3.corda.lib.tokens.contracts.utilities.AmountUtilities.amount;
8 | import static com.r3.corda.lib.tokens.testing.states.Rubles.RUB;
9 |
10 | public class FungibleTokenJavaTests extends ContractTestCommon {
11 | @Test
12 | public void testFungibleToken() {
13 | IssuedTokenType issuedRubles = new IssuedTokenType(ALICE.getParty(), RUB);
14 | new FungibleToken(amount(10, issuedRubles), ALICE.getParty());
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/contracts/src/test/java/com/r3/corda/lib/tokens/contracts/NonFungibleTokenJavaTests.java:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.contracts;
2 |
3 | import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken;
4 | import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType;
5 | import net.corda.core.contracts.UniqueIdentifier;
6 | import org.junit.Test;
7 |
8 | import static com.r3.corda.lib.tokens.testing.states.Rubles.RUB;
9 |
10 | public class NonFungibleTokenJavaTests extends ContractTestCommon {
11 | @Test
12 | public void testFungibleToken() {
13 | IssuedTokenType issuedRubles = new IssuedTokenType(ALICE.getParty(), RUB);
14 | new NonFungibleToken(issuedRubles, ALICE.getParty(), new UniqueIdentifier());
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/contracts/src/test/kotlin/com/r3/corda/lib/tokens/contracts/CommonTokens.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.contracts
2 |
3 | import com.r3.corda.lib.tokens.contracts.types.TokenType
4 | import net.corda.core.contracts.Amount
5 |
6 | class CommonTokens {
7 |
8 | companion object {
9 | val USD = TokenType("USD", 2)
10 | val GBP = TokenType("GBP", 2)
11 | }
12 |
13 | }
14 |
15 | fun Number.ofType(tt: TokenType): Amount {
16 | return Amount(this.toLong(), tt)
17 | }
--------------------------------------------------------------------------------
/contracts/src/test/kotlin/com/r3/corda/lib/tokens/contracts/TestEvolvableTokenContract.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.contracts
2 |
3 | import com.r3.corda.lib.tokens.contracts.states.EvolvableTokenType
4 | import net.corda.core.contracts.BelongsToContract
5 | import net.corda.core.contracts.Contract
6 | import net.corda.core.contracts.UniqueIdentifier
7 | import net.corda.core.identity.Party
8 | import net.corda.core.transactions.LedgerTransaction
9 |
10 | class TestEvolvableTokenContract : EvolvableTokenContract(), Contract {
11 | companion object {
12 | val ID: String = this::class.java.enclosingClass.canonicalName
13 | }
14 |
15 | override fun additionalCreateChecks(tx: LedgerTransaction) = Unit
16 | override fun additionalUpdateChecks(tx: LedgerTransaction) = Unit
17 | }
18 |
19 | @BelongsToContract(TestEvolvableTokenContract::class)
20 | data class TestEvolvableTokenType(
21 | override val maintainers: List,
22 | val observers: List = emptyList(),
23 | override val participants: List = maintainers + observers,
24 | override val linearId: UniqueIdentifier = UniqueIdentifier()
25 | ) : EvolvableTokenType() {
26 |
27 | override val fractionDigits: Int get() = 0
28 | }
--------------------------------------------------------------------------------
/contracts/src/test/kotlin/com/r3/corda/lib/tokens/contracts/TokenPointerTests.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.contracts
2 |
3 | import com.r3.corda.lib.tokens.contracts.commands.Create
4 | import com.r3.corda.lib.tokens.contracts.commands.IssueTokenCommand
5 | import com.r3.corda.lib.tokens.contracts.states.FungibleToken
6 | import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken
7 | import com.r3.corda.lib.tokens.contracts.types.TokenPointer
8 | import com.r3.corda.lib.tokens.contracts.utilities.*
9 | import net.corda.core.crypto.Crypto
10 | import net.corda.core.crypto.SignableData
11 | import net.corda.core.crypto.SignatureMetadata
12 | import net.corda.core.crypto.sign
13 | import net.corda.core.transactions.TransactionBuilder
14 | import org.junit.Test
15 | import kotlin.test.assertEquals
16 |
17 | class TokenPointerTests : ContractTestCommon() {
18 | @Test
19 | fun test() {
20 | // Create the evolvable token type.
21 | val evolvableToken = TestEvolvableTokenType(listOf(ALICE.party))
22 | val committedTransaction = aliceServices.run {
23 | val transaction = TransactionBuilder(notary = NOTARY.party).apply {
24 | addCommand(Create(), ALICE.publicKey)
25 | addOutputState(evolvableToken)
26 | }
27 | val signedTransaction = signInitialTransaction(transaction)
28 | recordTransactions(signedTransaction)
29 | signedTransaction
30 | }
31 | val outputStateAndRef = committedTransaction.tx.outRefsOfType().single()
32 | val tokenPointer = evolvableToken.toPointer()
33 | // Create a transaction which contains a state with a pointer to the above evolvable token type.
34 | val testTransaction = aliceServices.run {
35 | val transaction = TransactionBuilder(notary = NOTARY.party).apply {
36 | // Must add the reference state manually as VaultService is not implemented for MockServices.
37 | addReferenceState(outputStateAndRef.referenced())
38 | addOutputState(100 of tokenPointer issuedBy ALICE.party heldBy ALICE.party)
39 | addCommand(IssueTokenCommand(tokenPointer issuedBy ALICE.party), ALICE.publicKey)
40 | }
41 | val signedTransaction = signInitialTransaction(transaction)
42 | val signaureMetadata = SignatureMetadata(myInfo.platformVersion, Crypto.findSignatureScheme(NOTARY.publicKey).schemeNumberID)
43 | val signableData = SignableData(signedTransaction.id, signaureMetadata)
44 | val signature = NOTARY.keyPair.sign(signableData)
45 | val transactionWithNotarySignature = signedTransaction + signature
46 | recordTransactions(transactionWithNotarySignature)
47 | transactionWithNotarySignature
48 | }
49 | // Check we can resolve the pointer inside the ledger transaction.
50 | val ledgerTransaction = testTransaction.toLedgerTransaction(aliceServices)
51 | val fungibleToken = ledgerTransaction.singleOutput()
52 | assertEquals(((fungibleToken.tokenType) as TokenPointer).pointer.resolve(ledgerTransaction), outputStateAndRef)
53 | }
54 |
55 | @Test
56 | fun `tokenTypeJarHash must be not null if tokenType is not a pointer`() {
57 | val pointer: TokenPointer = TestEvolvableTokenType(listOf(ALICE.party)).toPointer()
58 | val pointerToken: NonFungibleToken = pointer issuedBy ISSUER.party heldBy ALICE.party
59 | val staticToken: NonFungibleToken = CommonTokens.GBP issuedBy ISSUER.party heldBy ALICE.party
60 | assertEquals(pointerToken.tokenTypeJarHash, null)
61 | assertEquals(staticToken.tokenTypeJarHash, CommonTokens.GBP.getAttachmentIdForGenericParam())
62 | }
63 | }
--------------------------------------------------------------------------------
/design/banking-system.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corda/token-sdk/749483f691c2c6ca56d9caacdd73a9e6319a969e/design/banking-system.png
--------------------------------------------------------------------------------
/design/chain-of-agreements.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corda/token-sdk/749483f691c2c6ca56d9caacdd73a9e6319a969e/design/chain-of-agreements.png
--------------------------------------------------------------------------------
/design/new-state-hierarchy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corda/token-sdk/749483f691c2c6ca56d9caacdd73a9e6319a969e/design/new-state-hierarchy.png
--------------------------------------------------------------------------------
/design/pointer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corda/token-sdk/749483f691c2c6ca56d9caacdd73a9e6319a969e/design/pointer.png
--------------------------------------------------------------------------------
/design/state-hierarchy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corda/token-sdk/749483f691c2c6ca56d9caacdd73a9e6319a969e/design/state-hierarchy.png
--------------------------------------------------------------------------------
/design/taxonomy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corda/token-sdk/749483f691c2c6ca56d9caacdd73a9e6319a969e/design/taxonomy.png
--------------------------------------------------------------------------------
/design/token-type-fungible-token.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corda/token-sdk/749483f691c2c6ca56d9caacdd73a9e6319a969e/design/token-type-fungible-token.png
--------------------------------------------------------------------------------
/deterministic.gradle:
--------------------------------------------------------------------------------
1 | import static org.gradle.api.JavaVersion.VERSION_1_8
2 |
3 | configurations {
4 | compileClasspath { Configuration c -> deterministic(c) }
5 | //runtimeClasspath { Configuration c -> deterministic(c) }
6 | }
7 |
8 | private final void deterministic(Configuration configuration) {
9 | if (configuration.state == Configuration.State.UNRESOLVED) {
10 | // Ensure that this module uses the deterministic Corda artifacts.
11 | configuration.resolutionStrategy.dependencySubstitution {
12 | substitute module("$corda_release_group:corda-serialization") with module("$corda_release_group:corda-serialization-deterministic:$corda_release_version")
13 | substitute module("$corda_release_group:corda-core") with module("$corda_release_group:corda-core-deterministic:$corda_release_version")
14 | }
15 | }
16 | }
17 |
18 | tasks.withType(JavaCompile) {
19 | // The DJVM only supports byte-code up to Java 8.
20 | sourceCompatibility = VERSION_1_8
21 | targetCompatibility = VERSION_1_8
22 | }
23 |
24 | tasks.withType(AbstractCompile) {
25 | // This is a bit ugly, but Gradle isn't recognising the KotlinCompile task
26 | // as it does the built-in JavaCompile task.
27 | if (it.class.name.startsWith('org.jetbrains.kotlin.gradle.tasks.KotlinCompile')) {
28 | kotlinOptions {
29 | // The DJVM only supports byte-code up to Java 8.
30 | jvmTarget = VERSION_1_8
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/freighter-tests/build.gradle:
--------------------------------------------------------------------------------
1 | import org.apache.tools.ant.taskdefs.condition.Os
2 |
3 | apply plugin: 'kotlin'
4 | apply plugin: 'idea'
5 |
6 |
7 | repositories {
8 | maven { url "${publicArtifactURL}/freighter-dev" }
9 | }
10 |
11 | sourceSets {
12 | freighterTest {
13 | kotlin {
14 | compileClasspath += main.output + test.output
15 | runtimeClasspath += main.output + test.output
16 | srcDir file('src/freighterTest/kotlin')
17 | }
18 | }
19 | }
20 |
21 | evaluationDependsOn(":workflows")
22 | task freighterTest(type: Test, dependsOn: [project(":workflows").jar]) {
23 | maxParallelForks 2
24 | systemProperty "java.util.concurrent.ForkJoinPool.common.parallelism", "128"
25 | testClassesDirs = sourceSets.freighterTest.output.classesDirs
26 | classpath = sourceSets.freighterTest.runtimeClasspath
27 | useJUnitPlatform {
28 | includeTags "DOCKER"
29 | excludeTags "AZURE", "FULL_LINUX_KERNEL", "ORACLE"
30 | }
31 | }
32 |
33 | configurations {
34 | freighterTestCompile.extendsFrom testCompile
35 | freighterTestRuntime.extendsFrom testRuntime
36 | }
37 |
38 | dependencies {
39 | freighterTestCompile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
40 | freighterTestCompile "freighter:freighter-testing-core-junit5:0.7.3-TEST-SNAPSHOT"
41 |
42 | freighterTestCompile project(":contracts")
43 | freighterTestCompile project(":workflows")
44 | }
45 |
46 |
47 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
48 | kotlinOptions {
49 | languageVersion = "1.2"
50 | apiVersion = "1.2"
51 | jvmTarget = "1.8"
52 | javaParameters = true // Useful for reflection.
53 | }
54 | }
55 |
56 |
57 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # token-sdk release version to be published.
2 | version=1.2.4
3 | # This versionSuffix value should be left blank for an external release (GA) or set to RC / HC for internal releases.
4 | # No hyphen prefix needed. versionSuffix left on SNAPSHOT standard release branch / feature branch activity
5 | versionSuffix=SNAPSHOT
6 |
7 | name=Token SDK
8 | group=com.r3.corda.lib.tokens
9 |
10 | kotlin.incremental=false
11 | org.gradle.caching = false
12 | gradleEnterpriseUrl = https://gradle.dev.r3.com
13 | gradleEnterprisePlugin = 3.8.1
14 | customUserDataGradlePlugin = 1.6.3
15 | gradleTestRetryPlugin = 1.4.0
16 |
17 | # Artifactory
18 | artifactoryContextUrl = https://software.r3.com/artifactory
19 | publicArtifactURL = https://download.corda.net/maven
20 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corda/token-sdk/749483f691c2c6ca56d9caacdd73a9e6319a969e/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/lib/README.txt:
--------------------------------------------------------------------------------
1 | The Quasar.jar in this directory is for runtime instrumentation of classes by Quasar.
2 |
3 | When running corda outside of the given gradle building you must add the following flag with the
4 | correct path to your call to Java:
5 |
6 | java -javaagent:path-to-quasar-jar.jar ...
7 |
8 | See the Quasar docs for more information: http://docs.paralleluniverse.co/quasar/
--------------------------------------------------------------------------------
/lib/quasar.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corda/token-sdk/749483f691c2c6ca56d9caacdd73a9e6319a969e/lib/quasar.jar
--------------------------------------------------------------------------------
/modules/contracts-for-testing/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'net.corda.plugins.cordapp'
2 |
3 | cordapp {
4 | targetPlatformVersion 6
5 | minimumPlatformVersion 6
6 | contract {
7 | name "Token SDK test-contracts"
8 | vendor "R3"
9 | licence "Apache 2"
10 | versionId 1
11 | }
12 | signing {
13 | enabled false
14 | }
15 | }
16 |
17 | sourceSets {
18 | main {
19 | resources {
20 | srcDir rootProject.file("config/dev")
21 | }
22 | }
23 | test {
24 | resources {
25 | srcDir rootProject.file("config/test")
26 | }
27 | }
28 | }
29 |
30 | dependencies {
31 | // Kotlin.
32 | compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
33 |
34 | // Corda dependencies.
35 | cordaCompile ("$corda_release_group:corda-core:$corda_release_version"){
36 | changing = true
37 | }
38 |
39 | // Logging.
40 | testCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
41 |
42 | // Testing.
43 | testCompile "$corda_release_group:corda-node-driver:$corda_release_version"
44 | testCompile "junit:junit:$junit_version"
45 | testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
46 |
47 | // CorDapp dependencies.
48 | cordapp project(":contracts")
49 | }
--------------------------------------------------------------------------------
/modules/contracts-for-testing/src/main/kotlin/com/r3/corda/lib/tokens/testing/contracts/DiamondGradingReportContract.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.testing.contracts
2 |
3 | import com.r3.corda.lib.tokens.contracts.EvolvableTokenContract
4 | import com.r3.corda.lib.tokens.testing.states.DiamondGradingReport
5 | import net.corda.core.contracts.Contract
6 | import net.corda.core.contracts.requireThat
7 | import net.corda.core.transactions.LedgerTransaction
8 | import java.math.BigDecimal
9 |
10 | /**
11 | * The [DiamondGradingReportContract] is inspired by the grading reports issued by the Gemological Institute of America
12 | * (GIA). For more excellent information on diamond grading, please see the (GIA's website)[http://www.gia.edu].
13 | */
14 | class DiamondGradingReportContract : EvolvableTokenContract(), Contract {
15 |
16 | override fun additionalCreateChecks(tx: LedgerTransaction) {
17 | val outputDiamond = tx.outputsOfType().first()
18 | requireThat {
19 | "Diamond's carat weight must be greater than 0 (zero)" using (outputDiamond.caratWeight > BigDecimal.ZERO)
20 | }
21 | }
22 |
23 | override fun additionalUpdateChecks(tx: LedgerTransaction) {
24 | val inDiamond = tx.outputsOfType().first()
25 | val outDiamond = tx.outputsOfType().first()
26 | requireThat {
27 | "Diamond's carat weight may not be changed" using (inDiamond.caratWeight == outDiamond.caratWeight)
28 | "Diamond's color may not be changed" using (inDiamond.color == outDiamond.color)
29 | "Diamond's clarity may not be changed" using (inDiamond.clarity == outDiamond.clarity)
30 | "Diamond's cut may not be changed" using (inDiamond.cut == outDiamond.cut)
31 | }
32 | }
33 |
34 |
35 | }
--------------------------------------------------------------------------------
/modules/contracts-for-testing/src/main/kotlin/com/r3/corda/lib/tokens/testing/contracts/HouseContract.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.testing.contracts
2 |
3 | import com.r3.corda.lib.tokens.contracts.EvolvableTokenContract
4 | import com.r3.corda.lib.tokens.testing.states.House
5 | import net.corda.core.contracts.Amount
6 | import net.corda.core.contracts.Contract
7 | import net.corda.core.transactions.LedgerTransaction
8 |
9 | // TODO: When contract scanning bug is fixed then this does not need to implement Contract.
10 | class HouseContract : EvolvableTokenContract(), Contract {
11 |
12 | override fun additionalCreateChecks(tx: LedgerTransaction) {
13 | // Not much to do for this example token.
14 | val newHouse = tx.outputStates.single() as House
15 | newHouse.apply {
16 | require(valuation > Amount.zero(valuation.token)) { "Valuation must be greater than zero." }
17 | }
18 | }
19 |
20 | override fun additionalUpdateChecks(tx: LedgerTransaction) {
21 | val oldHouse = tx.inputStates.single() as House
22 | val newHouse = tx.outputStates.single() as House
23 | require(oldHouse.address == newHouse.address) { "The address cannot change." }
24 | require(newHouse.valuation > Amount.zero(newHouse.valuation.token)) { "Valuation must be greater than zero." }
25 | }
26 |
27 | }
--------------------------------------------------------------------------------
/modules/contracts-for-testing/src/main/kotlin/com/r3/corda/lib/tokens/testing/contracts/TestEvolvableTokenContract.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.testing.contracts
2 |
3 | import com.r3.corda.lib.tokens.contracts.EvolvableTokenContract
4 | import net.corda.core.contracts.Contract
5 | import net.corda.core.contracts.requireThat
6 | import net.corda.core.transactions.LedgerTransaction
7 |
8 | class TestEvolvableTokenContract : EvolvableTokenContract(), Contract {
9 |
10 | companion object {
11 | val ID: String = this::class.java.enclosingClass.canonicalName
12 | }
13 |
14 | override fun additionalCreateChecks(tx: LedgerTransaction) {
15 | requireThat {
16 | // No additional checks
17 | }
18 | }
19 |
20 | override fun additionalUpdateChecks(tx: LedgerTransaction) {
21 | requireThat {
22 | // No additional checks
23 | }
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/modules/contracts-for-testing/src/main/kotlin/com/r3/corda/lib/tokens/testing/states/DiamondGradingReport.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.testing.states
2 |
3 | import com.r3.corda.lib.tokens.contracts.states.EvolvableTokenType
4 | import com.r3.corda.lib.tokens.testing.contracts.DiamondGradingReportContract
5 | import net.corda.core.contracts.BelongsToContract
6 | import net.corda.core.contracts.UniqueIdentifier
7 | import net.corda.core.identity.Party
8 | import net.corda.core.serialization.CordaSerializable
9 | import java.math.BigDecimal
10 |
11 | /**
12 | * The [DiamondGradingReport] is inspired by the grading reports issued by the Gemological Institute of America
13 | * (GIA). For more excellent information on diamond grading, please see the (GIA's website)[http://www.gia.edu].
14 | */
15 | @BelongsToContract(DiamondGradingReportContract::class)
16 | data class DiamondGradingReport(
17 | val caratWeight: BigDecimal,
18 | val color: ColorScale,
19 | val clarity: ClarityScale,
20 | val cut: CutScale,
21 | val assessor: Party,
22 | val requester: Party,
23 | override val linearId: UniqueIdentifier = UniqueIdentifier()
24 | ) : EvolvableTokenType() {
25 | constructor(
26 | caratWeight: String,
27 | color: ColorScale,
28 | clarity: ClarityScale,
29 | cut: CutScale,
30 | assessor: Party,
31 | requester: Party,
32 | linearId: UniqueIdentifier = UniqueIdentifier()) : this(BigDecimal(caratWeight), color, clarity, cut, assessor, requester, linearId)
33 |
34 | @CordaSerializable
35 | enum class ColorScale { A, B, C, D, E, F }
36 |
37 | @CordaSerializable
38 | enum class ClarityScale { A, B, C, D, E, F }
39 |
40 | @CordaSerializable
41 | enum class CutScale { A, B, C, D, E, F }
42 |
43 | override val maintainers get() = listOf(assessor)
44 |
45 | override val participants get() = setOf(assessor, requester).toList()
46 |
47 | override val fractionDigits: Int get() = 0
48 | }
--------------------------------------------------------------------------------
/modules/contracts-for-testing/src/main/kotlin/com/r3/corda/lib/tokens/testing/states/House.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.testing.states
2 |
3 | import com.r3.corda.lib.tokens.contracts.states.EvolvableTokenType
4 | import com.r3.corda.lib.tokens.contracts.types.TokenType
5 | import com.r3.corda.lib.tokens.testing.contracts.HouseContract
6 | import net.corda.core.contracts.Amount
7 | import net.corda.core.contracts.BelongsToContract
8 | import net.corda.core.contracts.UniqueIdentifier
9 | import net.corda.core.identity.Party
10 |
11 | // A token representing a house on ledger.
12 | @BelongsToContract(HouseContract::class)
13 | data class House(
14 | val address: String,
15 | val valuation: Amount,
16 | override val maintainers: List,
17 | override val fractionDigits: Int = 5,
18 | override val linearId: UniqueIdentifier
19 | ) : EvolvableTokenType()
--------------------------------------------------------------------------------
/modules/contracts-for-testing/src/main/kotlin/com/r3/corda/lib/tokens/testing/states/Rubles.kt:
--------------------------------------------------------------------------------
1 | @file:JvmName("Rubles")
2 | package com.r3.corda.lib.tokens.testing.states
3 |
4 | import com.r3.corda.lib.tokens.contracts.FungibleTokenContract
5 | import com.r3.corda.lib.tokens.contracts.states.FungibleToken
6 | import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType
7 | import com.r3.corda.lib.tokens.contracts.types.TokenType
8 | import net.corda.core.contracts.Amount
9 | import net.corda.core.contracts.BelongsToContract
10 | import net.corda.core.contracts.Contract
11 | import net.corda.core.identity.AbstractParty
12 | import net.corda.core.transactions.LedgerTransaction
13 |
14 |
15 | class Ruble : TokenType("рубль", 0) {
16 | override fun equals(other: Any?): Boolean {
17 | if (this === other) return true
18 | if (javaClass != other?.javaClass) return false
19 | if (!super.equals(other)) return false
20 | return true
21 | }
22 | }
23 |
24 | class PhoBowl : TokenType("PTK", 0) {
25 | override fun equals(other: Any?): Boolean {
26 | if (this === other) return true
27 | if (javaClass != other?.javaClass) return false
28 | if (!super.equals(other)) return false
29 | return true
30 | }
31 |
32 | }
33 |
34 | @JvmField
35 | val RUB = Ruble()
36 | @JvmField
37 | val PTK = PhoBowl()
38 |
39 | data class Appartment(val id: String = "Foo") : TokenType(id, 0)
40 |
41 | /**
42 | * Test class only used to test that are grouped by Contract as well as TokenType
43 | */
44 | @BelongsToContract(DodgeTokenContract::class)
45 | open class DodgeToken(amount: Amount,
46 | holder: AbstractParty) : FungibleToken(amount, holder)
47 |
48 | open class DodgeTokenContract : Contract {
49 | override fun verify(tx: LedgerTransaction) {
50 | }
51 | }
52 |
53 | /**
54 | * Test class only used to test that tokens cannot change class during a move
55 | */
56 | @BelongsToContract(FungibleTokenContract::class)
57 | open class RubleToken(amount: Amount,
58 | holder: AbstractParty) : FungibleToken(amount, holder)
--------------------------------------------------------------------------------
/modules/contracts-for-testing/src/main/kotlin/com/r3/corda/lib/tokens/testing/states/TestEvolvableTokenType.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.testing.states
2 |
3 | import com.r3.corda.lib.tokens.contracts.states.EvolvableTokenType
4 | import com.r3.corda.lib.tokens.testing.contracts.TestEvolvableTokenContract
5 | import net.corda.core.contracts.BelongsToContract
6 | import net.corda.core.contracts.UniqueIdentifier
7 | import net.corda.core.identity.Party
8 |
9 | @BelongsToContract(TestEvolvableTokenContract::class)
10 | data class TestEvolvableTokenType(
11 | override val maintainers: List,
12 | val observers: List = emptyList(),
13 | override val participants: List = (maintainers + observers),
14 | override val linearId: UniqueIdentifier = UniqueIdentifier(),
15 | override val fractionDigits: Int = 0
16 | ) : EvolvableTokenType()
--------------------------------------------------------------------------------
/modules/selection/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'net.corda.plugins.cordapp'
2 |
3 | description 'token-sdk token selection'
4 |
5 | cordapp {
6 | targetPlatformVersion 6
7 | minimumPlatformVersion 6
8 | contract {
9 | name "Token SDK test-selection"
10 | vendor "R3"
11 | licence "Apache 2"
12 | versionId 1
13 | }
14 | signing {
15 | enabled false
16 | }
17 | }
18 |
19 | sourceSets {
20 | main {
21 | resources {
22 | srcDir rootProject.file("config/dev")
23 | }
24 | }
25 | test {
26 | resources {
27 | srcDir rootProject.file("config/test")
28 | }
29 | }
30 | }
31 |
32 | dependencies {
33 | // Kotlin.
34 | compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
35 |
36 | // Corda dependencies.
37 | cordaCompile ("$corda_release_group:corda-core:$corda_release_version"){
38 | changing = true
39 | }
40 | // Corda dependencies.
41 | cordaCompile ("$corda_release_group:corda-node-api:$corda_release_version"){
42 | changing = true
43 | }
44 |
45 | // Logging.
46 | testCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
47 |
48 | // Testing.
49 | testCompile "$corda_release_group:corda-node-driver:$corda_release_version"
50 | testCompile "junit:junit:$junit_version"
51 | testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
52 |
53 | // CorDapp dependencies.
54 | cordapp project(":contracts")
55 | }
56 |
57 | jar {
58 | baseName "tokens-selection"
59 | }
--------------------------------------------------------------------------------
/modules/selection/src/main/kotlin/com.r3.corda.lib.tokens.selection/SelectionUtilities.kt:
--------------------------------------------------------------------------------
1 | @file:JvmName("SelectionUtilities")
2 | package com.r3.corda.lib.tokens.selection
3 |
4 | import co.paralleluniverse.fibers.Suspendable
5 | import com.r3.corda.lib.tokens.contracts.internal.schemas.PersistentFungibleToken
6 | import com.r3.corda.lib.tokens.contracts.types.TokenType
7 | import net.corda.core.CordaRuntimeException
8 | import net.corda.core.identity.AbstractParty
9 | import net.corda.core.identity.Party
10 | import net.corda.core.node.services.vault.QueryCriteria
11 | import net.corda.core.node.services.vault.Sort
12 | import net.corda.core.node.services.vault.SortAttribute
13 | import net.corda.core.node.services.vault.builder
14 |
15 | // TODO clean up the module structure of token-sdk, because these function and types (eg PartyAndAmount) should be separate from workflows
16 | // Sorts a query by state ref ascending.
17 | internal fun sortByStateRefAscending(): Sort {
18 | val sortAttribute = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF)
19 | return Sort(setOf(Sort.SortColumn(sortAttribute, Sort.Direction.ASC)))
20 | }
21 |
22 | internal fun sortByTimeStampAscending(): Sort {
23 | val sortAttribute = SortAttribute.Standard(Sort.VaultStateAttribute.RECORDED_TIME)
24 | return Sort(setOf(Sort.SortColumn(sortAttribute, Sort.Direction.ASC)))
25 | }
26 |
27 | // Returns all held token amounts of a specified token with given issuer.
28 | // We need to discriminate on the token type as well as the symbol as different tokens might use the same symbols.
29 | @Suspendable
30 | fun tokenAmountWithIssuerCriteria(token: TokenType, issuer: Party): QueryCriteria {
31 | val issuerCriteria = QueryCriteria.VaultCustomQueryCriteria(builder {
32 | PersistentFungibleToken::issuer.equal(issuer)
33 | })
34 | return tokenAmountCriteria(token).and(issuerCriteria)
35 | }
36 |
37 | // Returns all held token amounts of a specified token.
38 | // We need to discriminate on the token type as well as the symbol as different tokens might use the same symbols.
39 | @Suspendable
40 | fun tokenAmountCriteria(token: TokenType): QueryCriteria {
41 | val tokenClass = builder {
42 | PersistentFungibleToken::tokenClass.equal(token.tokenClass)
43 | }
44 | val tokenClassCriteria = QueryCriteria.VaultCustomQueryCriteria(tokenClass)
45 | val tokenIdentifier = builder {
46 | PersistentFungibleToken::tokenIdentifier.equal(token.tokenIdentifier)
47 | }
48 | val tokenIdentifierCriteria = QueryCriteria.VaultCustomQueryCriteria(tokenIdentifier)
49 | return tokenClassCriteria.and(tokenIdentifierCriteria)
50 | }
51 |
52 | @Suspendable
53 | fun tokenAmountWithHolderCriteria(token: TokenType, holder: AbstractParty): QueryCriteria {
54 | val issuerCriteria = QueryCriteria.VaultCustomQueryCriteria(builder {
55 | PersistentFungibleToken::holder.equal(holder)
56 | })
57 | return tokenAmountCriteria(token).and(issuerCriteria)
58 | }
59 |
60 | /**
61 | * An exception that is thrown where the specified criteria returns an amount of tokens
62 | * that is not sufficient for the specified spend. If the amount of tokens *is* sufficient
63 | * but there is not enough of non-locked tokens available to satisfy the amount then
64 | * [InsufficientNotLockedBalanceException] will be thrown.
65 | *
66 | * @param message The exception message that should be thrown in this context
67 | */
68 | open class InsufficientBalanceException(message: String) : CordaRuntimeException(message)
69 |
70 | /**
71 | * An exception that is thrown where the specified criteria returns an amount of tokens
72 | * that is sufficient for the specified spend, however there is not enough of non-locked tokens
73 | * available to satisfy the amount.
74 | *
75 | * @param message The exception message that should be thrown in this context
76 | */
77 | class InsufficientNotLockedBalanceException(message: String) : InsufficientBalanceException(message)
--------------------------------------------------------------------------------
/modules/selection/src/main/kotlin/com.r3.corda.lib.tokens.selection/TokenQueryBy.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.selection
2 |
3 | import com.r3.corda.lib.tokens.contracts.states.FungibleToken
4 | import net.corda.core.contracts.StateAndRef
5 | import net.corda.core.identity.Party
6 | import net.corda.core.node.services.vault.QueryCriteria
7 |
8 | //TODO: After 2.0 we should get rid of queryCriteria, because it was a mistake to expose it in the
9 | data class TokenQueryBy @JvmOverloads constructor(
10 | val issuer: Party? = null,
11 | val predicate: (StateAndRef) -> Boolean = { true }, val queryCriteria: QueryCriteria? = null)
12 |
13 | internal fun TokenQueryBy.issuerAndPredicate(): (StateAndRef) -> Boolean {
14 | return if (issuer != null) {
15 | { stateAndRef -> stateAndRef.state.data.amount.token.issuer == issuer && predicate(stateAndRef) }
16 | } else predicate
17 | }
--------------------------------------------------------------------------------
/modules/selection/src/main/kotlin/com.r3.corda.lib.tokens.selection/api/SateSelectionConfig.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.selection.api
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.selection.database.config.DatabaseSelectionConfig
5 | import com.r3.corda.lib.tokens.selection.memory.config.InMemorySelectionConfig
6 | import net.corda.core.cordapp.CordappConfig
7 | import net.corda.core.node.ServiceHub
8 | import org.slf4j.LoggerFactory
9 |
10 | interface StateSelectionConfig {
11 | @Suspendable
12 | fun toSelector(services: ServiceHub): Selector
13 | }
14 |
15 | /**
16 | * CorDapp config format:
17 | *
18 | * stateSelection {
19 | * database {
20 | * maxRetries: Int
21 | * retrySleep: Int
22 | * retryCap: Int
23 | * pageSize: Int
24 | * }
25 | * or
26 | * in_memory {
27 | * indexingStrategy: ["external_id"|"public_key"|"token_only"]
28 | * cacheSize: Int
29 | * }
30 | * }
31 | *
32 | * Use ConfigSelection.getPreferredSelection to choose based on you cordapp config between database token selection and in memory one.
33 | * By default Move and Redeem methods use config to switch between them. If no config option is provided it will default to database
34 | * token selection.
35 | */
36 | object ConfigSelection {
37 | val logger = LoggerFactory.getLogger("configSelectionLogger")
38 | @Suspendable
39 | fun getPreferredSelection(services: ServiceHub, config: CordappConfig = services.getAppContext().config): Selector {
40 | val hasSelection = config.exists("stateSelection")
41 | return if (!hasSelection) {
42 | logger.warn("No configuration for state selection, defaulting to database selection.")
43 | DatabaseSelectionConfig().toSelector(services) // Return default database selection
44 | } else {
45 | if (config.exists("stateSelection.database")) {
46 | DatabaseSelectionConfig.parse(config).toSelector(services)
47 | } else if (config.exists("stateSelection.inMemory")) {
48 | InMemorySelectionConfig.parse(config).toSelector(services)
49 | } else {
50 | throw IllegalArgumentException("Provide correct state-selection type string in the config, see kdocs for ConfigSelection.")
51 | }
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/modules/selection/src/main/kotlin/com.r3.corda.lib.tokens.selection/database/config/DatabaseSelectionConfig.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.selection.database.config
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.selection.api.ConfigSelection
5 | import com.r3.corda.lib.tokens.selection.api.Selector
6 | import com.r3.corda.lib.tokens.selection.api.StateSelectionConfig
7 | import com.r3.corda.lib.tokens.selection.database.selector.DatabaseTokenSelection
8 | import com.r3.corda.lib.tokens.selection.memory.config.InMemorySelectionConfig
9 | import com.r3.corda.lib.tokens.selection.memory.config.getIntOrNull
10 | import net.corda.core.cordapp.CordappConfig
11 | import net.corda.core.node.ServiceHub
12 | import org.slf4j.LoggerFactory
13 |
14 | const val MAX_RETRIES_DEFAULT = 8
15 | const val RETRY_SLEEP_DEFAULT = 100
16 | const val RETRY_CAP_DEFAULT = 2000
17 | const val PAGE_SIZE_DEFAULT = 200
18 |
19 | data class DatabaseSelectionConfig @JvmOverloads constructor(
20 | val maxRetries: Int = MAX_RETRIES_DEFAULT,
21 | val retrySleep: Int = RETRY_SLEEP_DEFAULT,
22 | val retryCap: Int = RETRY_CAP_DEFAULT,
23 | val pageSize: Int = PAGE_SIZE_DEFAULT
24 | ) : StateSelectionConfig {
25 | companion object {
26 | @JvmStatic
27 | fun parse(config: CordappConfig): DatabaseSelectionConfig {
28 | val maxRetries = config.getIntOrNull("stateSelection.database.maxRetries") ?: MAX_RETRIES_DEFAULT
29 | val retrySleep = config.getIntOrNull("stateSelection.database.retrySleep") ?: RETRY_SLEEP_DEFAULT
30 | val retryCap = config.getIntOrNull("stateSelection.database.retryCap") ?: RETRY_CAP_DEFAULT
31 | val pageSize = config.getIntOrNull("stateSelection.database.pageSize") ?: PAGE_SIZE_DEFAULT
32 | ConfigSelection.logger.info("Found database token selection configuration with values maxRetries: $maxRetries, retrySleep: $retrySleep, retryCap: $retryCap, pageSize: $pageSize")
33 | return DatabaseSelectionConfig(maxRetries, retrySleep, retryCap, pageSize)
34 | }
35 | }
36 |
37 | @Suspendable
38 | override fun toSelector(services: ServiceHub): DatabaseTokenSelection {
39 | return DatabaseTokenSelection(services, maxRetries, retrySleep, retryCap, pageSize)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/modules/selection/src/main/kotlin/com.r3.corda.lib.tokens.selection/memory/config/InMemorySelectionConfig.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.selection.memory.config
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.selection.api.StateSelectionConfig
5 | import com.r3.corda.lib.tokens.selection.memory.selector.LocalTokenSelector
6 | import com.r3.corda.lib.tokens.selection.memory.services.VaultWatcherService
7 | import net.corda.core.cordapp.CordappConfig
8 | import net.corda.core.cordapp.CordappConfigException
9 | import net.corda.core.node.ServiceHub
10 | import org.slf4j.LoggerFactory
11 |
12 | const val CACHE_SIZE_DEFAULT = 1024 // TODO Return good default, for now it's not wired, it will be done in separate PR.
13 |
14 | data class InMemorySelectionConfig @JvmOverloads constructor(val enabled: Boolean,
15 | val indexingStrategies: List,
16 | val cacheSize: Int = CACHE_SIZE_DEFAULT) : StateSelectionConfig {
17 | companion object {
18 | private val logger = LoggerFactory.getLogger("inMemoryConfigSelectionLogger")
19 |
20 | @JvmStatic
21 | fun parse(config: CordappConfig): InMemorySelectionConfig {
22 | val enabled = if (!config.exists("stateSelection.inMemory.enabled")) {
23 | logger.warn("Did not detect a configuration for InMemory selection - enabling memory usage for token indexing. Please set stateSelection.inMemory.enabled to \"false\" to disable this")
24 | true
25 | } else {
26 | config.getBoolean("stateSelection.inMemory.enabled")
27 | }
28 | val cacheSize = config.getIntOrNull("stateSelection.inMemory.cacheSize")
29 | ?: CACHE_SIZE_DEFAULT
30 | val indexingType = try {
31 | (config.get("stateSelection.inMemory.indexingStrategies") as List).map { VaultWatcherService.IndexingType.valueOf(it.toString()) }
32 | } catch (e: CordappConfigException) {
33 | logger.warn("No indexing method specified. Indexes will be created at run-time for each invocation of selectTokens")
34 | emptyList()
35 | } catch (e: ClassCastException) {
36 | logger.warn("No indexing method specified. Indexes will be created at run-time for each invocation of selectTokens")
37 | emptyList()
38 | }
39 | logger.info("Found in memory token selection configuration with values indexing strategy: $indexingType, cacheSize: $cacheSize")
40 | return InMemorySelectionConfig(enabled, indexingType, cacheSize)
41 | }
42 |
43 | fun defaultConfig(): InMemorySelectionConfig {
44 | return InMemorySelectionConfig(true, emptyList())
45 | }
46 | }
47 |
48 | @Suspendable
49 | override fun toSelector(services: ServiceHub): LocalTokenSelector {
50 | return try {
51 | val vaultObserver = services.cordaService(VaultWatcherService::class.java)
52 | LocalTokenSelector(services, vaultObserver, state = null)
53 | } catch (e: IllegalArgumentException) {
54 | throw IllegalArgumentException("Couldn't find VaultWatcherService in CordaServices, please make sure that it was installed in node.")
55 | }
56 | }
57 | }
58 |
59 | // Helpers for configuration parsing.
60 |
61 | fun CordappConfig.getIntOrNull(path: String): Int? {
62 | return try {
63 | getInt(path)
64 | } catch (e: CordappConfigException) {
65 | if (exists(path)) {
66 | throw IllegalArgumentException("Provide correct database selection configuration for config path: $path")
67 | } else {
68 | null
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/modules/selection/src/main/kotlin/com.r3.corda.lib.tokens.selection/memory/internal/ExternalIdIndexUtils.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.selection.memory.internal
2 |
3 | import net.corda.core.node.ServiceHub
4 | import java.security.PublicKey
5 | import java.util.*
6 |
7 | sealed class Holder {
8 | data class KeyIdentity(val owningKey: PublicKey) : Holder() // Just public key
9 | class UnmappedIdentity : Holder() // For all keys that are unmapped
10 | {
11 | override fun equals(other: Any?): Boolean {
12 | return this === other
13 | }
14 |
15 | override fun hashCode(): Int {
16 | return System.identityHashCode(this)
17 | }
18 | }
19 |
20 | data class MappedIdentity(val uuid: UUID) : Holder() // All keys register to this uuid
21 | class TokenOnly : Holder() // This is for the case where we use token class and token identifier only
22 | {
23 | override fun equals(other: Any?): Boolean {
24 | return this === other
25 | }
26 |
27 | override fun hashCode(): Int {
28 | return System.identityHashCode(this)
29 | }
30 | }
31 |
32 | companion object {
33 | fun fromUUID(uuid: UUID?): Holder {
34 | return if (uuid != null) {
35 | MappedIdentity(uuid)
36 | } else {
37 | UnmappedIdentity()
38 | }
39 | }
40 | }
41 | }
42 |
43 | fun lookupExternalIdFromKey(owningKey: PublicKey, serviceHub: ServiceHub): Holder {
44 | val uuid = serviceHub.identityService.externalIdForPublicKey(owningKey)
45 | return if (uuid != null || isKeyPartOfNodeKeyPairs(owningKey, serviceHub) || isKeyIdentityKey(owningKey, serviceHub)) {
46 | val signingEntity = Holder.fromUUID(uuid)
47 | signingEntity
48 | } else {
49 | Holder.UnmappedIdentity()
50 | }
51 | }
52 |
53 | /**
54 | * Establish whether a public key is one of the node's identity keys, by looking in the node's identity database table.
55 | */
56 | private fun isKeyIdentityKey(key: PublicKey, services: ServiceHub): Boolean {
57 | val party = services.identityService.partyFromKey(key)
58 | return party?.owningKey == key
59 | }
60 |
61 | /**
62 | * Check to see if the key belongs to one of the key pairs in the node_our_key_pairs table. These keys may relate to confidential
63 | * identities.
64 | */
65 | private fun isKeyPartOfNodeKeyPairs(key: PublicKey, services: ServiceHub): Boolean {
66 | return services.keyManagementService.filterMyKeys(listOf(key)).toList().isNotEmpty()
67 | }
68 |
--------------------------------------------------------------------------------
/modules/selection/src/main/kotlin/com.r3.corda.lib.tokens.selection/memory/services/VaultMigratorService.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.selection.memory.services
2 |
3 | import net.corda.core.node.AppServiceHub
4 | import net.corda.core.node.services.CordaService
5 | import net.corda.core.serialization.SingletonSerializeAsToken
6 |
7 | @CordaService
8 | class VaultMigratorService(appServiceHub: AppServiceHub) : SingletonSerializeAsToken() {
9 | //TODO - we should attempt to migrate the old vault contents. This must be done a service because we cannot guarantee
10 | //the order of migration scripts and therefore cannot initiate hibernate
11 | }
12 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include 'contracts'
2 | include 'workflows'
3 | include 'workflows-integration-test'
4 | include 'modules:contracts-for-testing'
5 | include 'modules:selection'
6 | include 'freighter-tests'
7 |
8 |
--------------------------------------------------------------------------------
/workflows-integration-test/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'kotlin-jpa'
2 | apply plugin: 'net.corda.plugins.quasar-utils'
3 | apply plugin: 'net.corda.plugins.cordapp'
4 |
5 | cordapp {
6 | targetPlatformVersion 6
7 | minimumPlatformVersion 6
8 | workflow {
9 | name "Token SDK Workflows Integration Test"
10 | vendor "R3"
11 | licence "Apache 2"
12 | versionId 2
13 | }
14 | signing {
15 | enabled false
16 | }
17 | }
18 |
19 | sourceSets {
20 | main {
21 | resources {
22 | srcDir rootProject.file("config/dev")
23 | }
24 | }
25 | test {
26 | resources {
27 | srcDir rootProject.file("config/test")
28 | }
29 | }
30 | integrationTest {
31 | kotlin {
32 | compileClasspath += main.output + test.output
33 | runtimeClasspath += main.output + test.output
34 | srcDir file('src/integrationTest/kotlin')
35 | }
36 | }
37 | }
38 |
39 | configurations {
40 | integrationTestCompile.extendsFrom testCompile
41 | integrationTestRuntime.extendsFrom testRuntime
42 | }
43 |
44 | dependencies {
45 | // Kotlin.
46 | compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
47 |
48 | // Corda dependencies.
49 | cordaCompile("$corda_release_group:corda-core:$corda_release_version") {
50 | changing = true
51 | }
52 |
53 | cordaCompile("$corda_release_group:corda-node-api:$corda_release_version") {
54 | changing = true
55 | }
56 |
57 | // Logging.
58 | testCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
59 |
60 | // Testing.
61 | testCompile "$corda_release_group:corda-node-driver:$corda_release_version"
62 | testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
63 | testCompile "junit:junit:$junit_version"
64 |
65 | // CorDapp dependencies.
66 | cordapp project(":workflows")
67 |
68 | compileOnly project(":modules:contracts-for-testing")
69 | testCompile project(":modules:contracts-for-testing")
70 |
71 | compileOnly("org.postgresql:postgresql:42.2.8")
72 |
73 | }
74 |
75 | task integrationTest(type: Test, dependsOn: []) {
76 | testClassesDirs = sourceSets.integrationTest.output.classesDirs
77 | classpath = sourceSets.integrationTest.runtimeClasspath
78 | }
79 |
80 | jar {
81 | baseName "workflows-integration-test"
82 | }
--------------------------------------------------------------------------------
/workflows/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'kotlin-jpa'
2 | apply plugin: 'net.corda.plugins.quasar-utils'
3 | apply plugin: 'net.corda.plugins.cordapp'
4 |
5 | description 'token-sdk workflows'
6 |
7 | evaluationDependsOn(":modules:selection")
8 |
9 | def selectionProject = project(":modules:selection")
10 |
11 | cordapp {
12 | targetPlatformVersion 6
13 | minimumPlatformVersion 6
14 | workflow {
15 | name "Token SDK Workflows"
16 | vendor "R3"
17 | licence "Apache 2"
18 | versionId 2
19 | }
20 | signing {
21 | enabled false
22 | }
23 | }
24 |
25 | sourceSets {
26 | main {
27 | resources {
28 | srcDir rootProject.file("config/dev")
29 | }
30 | }
31 | test {
32 | resources {
33 | srcDir rootProject.file("config/test")
34 | }
35 | }
36 | integrationTest {
37 | kotlin {
38 | compileClasspath += main.output + test.output
39 | runtimeClasspath += main.output + test.output
40 | srcDir file('src/integrationTest/kotlin')
41 | }
42 | }
43 | }
44 |
45 | configurations {
46 | integrationTestCompile.extendsFrom testCompile
47 | integrationTestRuntime.extendsFrom testRuntime
48 | }
49 |
50 | compileKotlin{
51 | dependsOn (selectionProject.jar)
52 | }
53 |
54 | dependencies {
55 | // Kotlin.
56 | compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
57 |
58 | // Corda dependencies.
59 | cordaCompile("$corda_release_group:corda-core:$corda_release_version") {
60 | changing = true
61 | }
62 |
63 | cordaCompile("$corda_release_group:corda-node-api:$corda_release_version") {
64 | changing = true
65 | }
66 |
67 | cordaCompile("$corda_release_group:corda-core-test-utils:$corda_release_version") {
68 | changing = true
69 | }
70 |
71 | // Logging.
72 | testCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
73 |
74 | // Testing.
75 | testCompile "$corda_release_group:corda-node-driver:$corda_release_version"
76 | testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
77 | testCompile "junit:junit:$junit_version"
78 |
79 | // CorDapp dependencies.
80 | cordapp project(":contracts")
81 |
82 | compile(files(selectionProject.jar.archivePath))
83 |
84 | //CI for confidential tokens
85 | cordapp "$confidential_id_release_group:ci-workflows:$confidential_id_release_version"
86 |
87 | //just for testing - this means you MUST specify the contracts-for-testing cordapp as a cordapp in all tests using test flows
88 | compileOnly project(":modules:contracts-for-testing")
89 | testCompile project(":modules:contracts-for-testing")
90 |
91 | compileOnly("org.postgresql:postgresql:42.2.8")
92 |
93 | }
94 |
95 | task integrationTest(type: Test, dependsOn: []) {
96 | testClassesDirs = sourceSets.integrationTest.output.classesDirs
97 | classpath = sourceSets.integrationTest.runtimeClasspath
98 | }
99 |
100 | jar {
101 | baseName "tokens-workflows"
102 | }
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/money/DigitalCurrency.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.money
2 |
3 | import com.r3.corda.lib.tokens.contracts.types.TokenType
4 | import java.util.*
5 |
6 | /**
7 | * A representation of digital money. This implementation somewhat mirrors that of [Currency].
8 | *
9 | * Note that the primary constructor only exists for simple and convenient access from Java code.
10 | *
11 | * @param currencyCode The currency code that represents the TokenType which the developer wishes to instantiate.
12 | */
13 | class DigitalCurrency {
14 | companion object {
15 | private val registry = mapOf(
16 | Pair("XRP", TokenType("Ripple", 6)),
17 | Pair("BTC", TokenType("Bitcoin", 8)),
18 | Pair("ETH", TokenType("Ethereum", 18)),
19 | Pair("DOGE", TokenType("Dogecoin", 8))
20 | )
21 |
22 | @JvmStatic
23 | fun getInstance(currencyCode: String): TokenType {
24 | return registry[currencyCode] ?: throw IllegalArgumentException("$currencyCode doesn't exist.")
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/money/FiatCurrency.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.money
2 |
3 | import com.r3.corda.lib.tokens.contracts.types.TokenType
4 | import java.util.*
5 |
6 | /**
7 | * This class is used to return a [TokenType] with the required currency code and fraction digits for fiat currencies.
8 | *
9 | * Note that the primary constructor only exists for simple and convenient access from Java code.
10 | *
11 | * @param currencyCode The currency code that represents the TokenType which the developer wishes to instantiate.
12 | */
13 | class FiatCurrency {
14 | companion object {
15 | // Uses the java money registry.
16 | @JvmStatic
17 | fun getInstance(currencyCode: String): TokenType {
18 | val currency = Currency.getInstance(currencyCode)
19 | return TokenType(currency.currencyCode, currency.defaultFractionDigits)
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/Exceptions.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows
2 |
3 | import net.corda.core.flows.FlowException
4 |
5 | /**
6 | * A dedicated exception for the TokenBuilder class.
7 | *
8 | * @param exceptionMessage The message to be included in the exception thrown.
9 | */
10 | class TokenBuilderException(exceptionMessage: String): FlowException(exceptionMessage)
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/confidential/ConfidentialTokensFlow.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.confidential
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.contracts.states.AbstractToken
5 | import com.r3.corda.lib.tokens.workflows.internal.flows.confidential.AnonymisePartiesFlow
6 | import com.r3.corda.lib.tokens.workflows.utilities.toWellKnownParties
7 | import net.corda.core.flows.FlowLogic
8 | import net.corda.core.flows.FlowSession
9 | import net.corda.core.identity.AnonymousParty
10 | import net.corda.core.identity.Party
11 |
12 |
13 | /**
14 | * This flow extracts the holders from a list of tokens to be issued on ledger, then requests only the well known
15 | * holders to generate a new key pair for holding the new asset. The new key pair effectively anonymises them. The
16 | * newly generated public keys replace the old, well known, keys. The flow doesn't request new keys for
17 | * [AnonymousParty]s.
18 | *
19 | * This is an in-line flow and use of it should be paired with [ConfidentialTokensFlowHandler].
20 | *
21 | * Note that this flow should only be called if you are dealing with [Party]s as individual nodes, i.e. not accounts.
22 | * When issuing tokens to accounts, the public keys + tokens need to be generated up-front as passed into the
23 | * [IssueTokensFlow].
24 | *
25 | * @property tokens a list of [AbstractToken]s.
26 | * @property sessions a list of participants' sessions which may contain sessions for observers.
27 | */
28 | class ConfidentialTokensFlow(
29 | val tokens: List,
30 | val sessions: List
31 | ) : FlowLogic>() {
32 | @Suspendable
33 | override fun call(): List {
34 | // Some holders might be anonymous already. E.g. if some token selection has been performed and a confidential
35 | // change address was requested.
36 | val tokensWithWellKnownHolders = tokens.filter { it.holder is Party }
37 | val tokensWithAnonymousHolders = tokens - tokensWithWellKnownHolders
38 | val wellKnownTokenHolders = tokensWithWellKnownHolders
39 | .map(AbstractToken::holder)
40 | .toWellKnownParties(serviceHub)
41 | val anonymousParties = subFlow(AnonymisePartiesFlow(wellKnownTokenHolders, sessions))
42 | // Replace Party with AnonymousParty.
43 | return tokensWithWellKnownHolders.map { token ->
44 | val holder = token.holder
45 | val anonymousParty = anonymousParties[holder]
46 | ?: throw IllegalStateException("Missing anonymous party for $holder.")
47 | token.withNewHolder(anonymousParty)
48 | } + tokensWithAnonymousHolders
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/confidential/ConfidentialTokensFlowHandler.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.confidential
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.workflows.internal.flows.confidential.AnonymisePartiesFlowHandler
5 | import net.corda.core.flows.FlowLogic
6 | import net.corda.core.flows.FlowSession
7 |
8 | /**
9 | * Use of this flow should be paired with [ConfidentialTokensFlow]. If asked to do so, this flow begins the generation
10 | * of a new key pair by calling [RequestConfidentialIdentityFlowHandler].
11 | */
12 | class ConfidentialTokensFlowHandler(val otherSession: FlowSession) : FlowLogic() {
13 | @Suspendable
14 | override fun call() = subFlow(AnonymisePartiesFlowHandler(otherSession))
15 | }
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/evolvable/CreateEvolvableTokensFlowHandler.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.evolvable
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.contracts.states.EvolvableTokenType
5 | import com.r3.corda.lib.tokens.workflows.internal.flows.finality.ObserverAwareFinalityFlowHandler
6 | import net.corda.core.contracts.requireThat
7 | import net.corda.core.crypto.SecureHash
8 | import net.corda.core.flows.FlowLogic
9 | import net.corda.core.flows.FlowSession
10 | import net.corda.core.flows.SignTransactionFlow
11 | import net.corda.core.transactions.SignedTransaction
12 | import net.corda.core.utilities.unwrap
13 |
14 | /** In-line counter-flow to [CreateEvolvableTokensFlow]. */
15 | class CreateEvolvableTokensFlowHandler(val otherSession: FlowSession) : FlowLogic() {
16 | @Suspendable
17 | override fun call() {
18 | // Receive the notification
19 | val notification = otherSession.receive().unwrap { it }
20 |
21 | var expectedTransactionId: SecureHash? = null
22 | // Sign the transaction proposal, if required
23 | if (notification.signatureRequired) {
24 | val signTransactionFlow = object : SignTransactionFlow(otherSession) {
25 | override fun checkTransaction(stx: SignedTransaction) = requireThat {
26 | val ledgerTransaction = stx.toLedgerTransaction(serviceHub, checkSufficientSignatures = false)
27 | val evolvableTokenTypeStateRefs = ledgerTransaction.outRefsOfType()
28 | val allMaintainers = evolvableTokenTypeStateRefs.map { it.state.data.maintainers }.flatten()
29 | require(ourIdentity in allMaintainers) {
30 | "Our node was asked to sign this transaction '${stx.id} but we are not a maintainer"
31 | }
32 | expectedTransactionId = stx.id
33 | }
34 | }
35 | subFlow(signTransactionFlow)
36 | }
37 |
38 | // Resolve the creation transaction.
39 | subFlow(ObserverAwareFinalityFlowHandler(otherSession, expectedTransactionId))
40 | }
41 | }
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/evolvable/EvolvableTokenUtilities.kt:
--------------------------------------------------------------------------------
1 | @file:JvmName("EvolvableTokenUtilities")
2 | package com.r3.corda.lib.tokens.workflows.flows.evolvable
3 |
4 | import com.r3.corda.lib.tokens.contracts.commands.Create
5 | import com.r3.corda.lib.tokens.contracts.commands.Update
6 | import com.r3.corda.lib.tokens.contracts.states.EvolvableTokenType
7 | import com.r3.corda.lib.tokens.contracts.utilities.withNotary
8 | import com.r3.corda.lib.tokens.workflows.internal.flows.distribution.getDistributionList
9 | import net.corda.core.contracts.ContractClassName
10 | import net.corda.core.contracts.StateAndRef
11 | import net.corda.core.contracts.TransactionState
12 | import net.corda.core.identity.AbstractParty
13 | import net.corda.core.identity.Party
14 | import net.corda.core.node.ServiceHub
15 | import net.corda.core.transactions.TransactionBuilder
16 |
17 | /**
18 | * Helper method for assembling a [Create] transaction for [EvolvableToken]s. This accepts a [TransactionBuilder] as well
19 | * as token details, and adds commands and outputs to the transaction. Returns the [TransactionBuilder]
20 | * to allow for continued assembly of the transaction, as needed.
21 | */
22 | fun addCreateEvolvableToken(
23 | transactionBuilder: TransactionBuilder,
24 | state: TransactionState
25 | ): TransactionBuilder {
26 | val maintainers = state.data.maintainers.toSet()
27 | val signingKeys = maintainers.map { it.owningKey }
28 | return transactionBuilder
29 | .addCommand(data = Create(), keys = signingKeys)
30 | .addOutputState(state = state)
31 | }
32 |
33 | fun addCreateEvolvableToken(
34 | transactionBuilder: TransactionBuilder,
35 | evolvableToken: T,
36 | contract: ContractClassName,
37 | notary: Party
38 | ): TransactionBuilder {
39 | return addCreateEvolvableToken(transactionBuilder, TransactionState(evolvableToken, contract, notary))
40 | }
41 |
42 | fun addCreateEvolvableToken(
43 | transactionBuilder: TransactionBuilder,
44 | evolvableToken: T,
45 | notary: Party
46 | ): TransactionBuilder {
47 | return addCreateEvolvableToken(transactionBuilder, evolvableToken withNotary notary)
48 | }
49 |
50 | /**
51 | * Helper method for assembling an [Update] transaction for [EvolvableToken]s. This accepts a [TransactionBuilder] as well
52 | * as old and new states, and adds commands, inputs, and outputs to the transaction. Returns the [TransactionBuilder]
53 | * to allow for continued assembly of the transaction, as needed.
54 | */
55 | fun addUpdateEvolvableToken(
56 | transactionBuilder: TransactionBuilder,
57 | oldStateAndRef: StateAndRef,
58 | newState: EvolvableTokenType
59 | ): TransactionBuilder {
60 | val oldState = oldStateAndRef.state.data
61 | val maintainers = (oldState.maintainers + newState.maintainers).toSet()
62 | val signingKeys = maintainers.map { it.owningKey }
63 | return transactionBuilder
64 | .addCommand(data = Update(), keys = signingKeys)
65 | .addInputState(oldStateAndRef)
66 | .addOutputState(state = newState, contract = oldStateAndRef.state.contract)
67 | }
68 |
69 | internal fun Iterable.maintainers(): Set = fold(emptySet(), { acc, txState -> acc.plus(txState.maintainers) })
70 |
71 | internal fun Iterable.participants(): Set = fold(emptySet(), { acc, txState -> acc.plus(txState.participants) })
72 |
73 | internal fun Iterable.otherMaintainers(ourIdentity: Party) = maintainers().minus(ourIdentity)
74 |
75 | internal fun subscribersForState(state: EvolvableTokenType, serviceHub: ServiceHub): Set {
76 | return getDistributionList(serviceHub, state.linearId).map { it.party }.toSet()
77 | }
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/evolvable/UpdateEvolvableTokenFlowHandler.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.evolvable
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.contracts.states.EvolvableTokenType
5 | import com.r3.corda.lib.tokens.workflows.internal.flows.finality.ObserverAwareFinalityFlowHandler
6 | import net.corda.core.contracts.requireThat
7 | import net.corda.core.crypto.SecureHash
8 | import net.corda.core.flows.FlowLogic
9 | import net.corda.core.flows.FlowSession
10 | import net.corda.core.flows.SignTransactionFlow
11 | import net.corda.core.transactions.SignedTransaction
12 | import net.corda.core.utilities.unwrap
13 |
14 | /** In-line counter-flow for [UpdateEvolvableTokenFlow]. */
15 | class UpdateEvolvableTokenFlowHandler(val otherSession: FlowSession) : FlowLogic() {
16 | @Suspendable
17 | override fun call() {
18 | // Receive the notification
19 | val notification = otherSession.receive().unwrap { it }
20 |
21 | var expectedTransactionId: SecureHash? = null
22 | // Sign the transaction proposal, if required
23 | if (notification.signatureRequired) {
24 | val signTransactionFlow = object : SignTransactionFlow(otherSession) {
25 | override fun checkTransaction(stx: SignedTransaction) = requireThat {
26 | val ledgerTransaction = stx.toLedgerTransaction(serviceHub, checkSufficientSignatures = false)
27 | val evolvableTokenTypeStateRef = ledgerTransaction.inRefsOfType().single()
28 | val oldMaintainers = evolvableTokenTypeStateRef.state.data.maintainers
29 | require(otherSession.counterparty in oldMaintainers) {
30 | "This flow can only be started by existing maintainers of the EvolvableTokenType. However it " +
31 | "was started by ${otherSession.counterparty} who is not a maintainer."
32 | }
33 | expectedTransactionId = stx.id
34 | }
35 | }
36 | subFlow(signTransactionFlow)
37 | }
38 |
39 | // Resolve the update transaction.
40 | subFlow(ObserverAwareFinalityFlowHandler(otherSession, expectedTransactionId))
41 | }
42 | }
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/issue/ConfidentialIssueTokensFlow.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.issue
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.contracts.states.AbstractToken
5 | import com.r3.corda.lib.tokens.contracts.states.FungibleToken
6 | import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken
7 | import com.r3.corda.lib.tokens.workflows.flows.confidential.ConfidentialTokensFlow
8 | import com.r3.corda.lib.tokens.workflows.internal.flows.finality.TransactionRole
9 | import net.corda.core.flows.FlowLogic
10 | import net.corda.core.flows.FlowSession
11 | import net.corda.core.transactions.SignedTransaction
12 |
13 | /**
14 | * A flow for issuing tokens to confidential keys. To be used in conjunction with the
15 | * [ConfidentialIssueTokensFlowHandler].
16 | *
17 | * @property tokens a list of tokens to issue.
18 | * @property participantSessions a list of sessions for the parties being issued tokens.
19 | * @property observerSessions a list of sessions for any observers.
20 | */
21 | class ConfidentialIssueTokensFlow
22 | @JvmOverloads
23 | constructor(
24 | val tokens: List,
25 | val participantSessions: List,
26 | val observerSessions: List = emptyList()
27 | ) : FlowLogic() {
28 |
29 | /** Issue a single [FungibleToken]. */
30 | @JvmOverloads
31 | constructor(
32 | token: FungibleToken,
33 | participantSessions: List,
34 | observerSessions: List = emptyList()
35 | ) : this(listOf(token), participantSessions, observerSessions)
36 |
37 | /** Issue a single [FungibleToken] to self with no observers. */
38 | constructor(token: FungibleToken) : this(listOf(token), emptyList(), emptyList())
39 |
40 | /** Issue a single [NonFungibleToken]. */
41 | @JvmOverloads
42 | constructor(
43 | token: NonFungibleToken,
44 | participantSessions: List,
45 | observerSessions: List = emptyList()
46 | ) : this(listOf(token), participantSessions, observerSessions)
47 |
48 | /** Issue a single [NonFungibleToken] to self with no observers. */
49 | constructor(token: NonFungibleToken) : this(listOf(token), emptyList(), emptyList())
50 |
51 | @Suspendable
52 | override fun call(): SignedTransaction {
53 | // TODO Not pretty fix, because we decided to go with sessions approach, we need to make sure that right responders are started depending on observer/participant role
54 | participantSessions.forEach { it.send(TransactionRole.PARTICIPANT) }
55 | observerSessions.forEach { it.send(TransactionRole.OBSERVER) }
56 | // Request new keys pairs from all proposed token holders.
57 | val confidentialTokens = subFlow(ConfidentialTokensFlow(tokens, participantSessions))
58 | // Issue tokens using the existing participantSessions.
59 | return subFlow(IssueTokensFlow(confidentialTokens, participantSessions, observerSessions))
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/issue/ConfidentialIssueTokensFlowHandler.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.issue
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.workflows.flows.confidential.ConfidentialTokensFlowHandler
5 | import com.r3.corda.lib.tokens.workflows.internal.flows.finality.TransactionRole
6 | import net.corda.core.flows.FlowLogic
7 | import net.corda.core.flows.FlowSession
8 | import net.corda.core.utilities.unwrap
9 |
10 | /**
11 | * The in-line flow handler for [ConfidentialIssueTokensFlow].
12 | */
13 | class ConfidentialIssueTokensFlowHandler(val otherSession: FlowSession) : FlowLogic() {
14 | @Suspendable
15 | override fun call() {
16 | // TODO This is nasty as soon as our flows become more involved we will end up with crazy responders
17 | val role = otherSession.receive().unwrap { it }
18 | if (role == TransactionRole.PARTICIPANT) {
19 | subFlow(ConfidentialTokensFlowHandler(otherSession))
20 | }
21 | subFlow(IssueTokensFlowHandler(otherSession))
22 | }
23 | }
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/issue/IssueTokensFlowHandler.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.issue
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.workflows.internal.flows.finality.ObserverAwareFinalityFlowHandler
5 | import net.corda.core.flows.FlowLogic
6 | import net.corda.core.flows.FlowSession
7 |
8 | /**
9 | * The in-line flow handler for [IssueTokensFlow].
10 | */
11 | class IssueTokensFlowHandler(val otherSession: FlowSession) : FlowLogic() {
12 | @Suspendable
13 | override fun call() {
14 | if (!serviceHub.myInfo.isLegalIdentity(otherSession.counterparty)) {
15 | subFlow(ObserverAwareFinalityFlowHandler(otherSession))
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/issue/IssueTokensUtilities.kt:
--------------------------------------------------------------------------------
1 | @file:JvmName("IssueTokensUtilities")
2 | package com.r3.corda.lib.tokens.workflows.flows.issue
3 |
4 | import co.paralleluniverse.fibers.Suspendable
5 | import com.r3.corda.lib.tokens.contracts.commands.IssueTokenCommand
6 | import com.r3.corda.lib.tokens.contracts.states.AbstractToken
7 | import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType
8 | import net.corda.core.identity.Party
9 | import net.corda.core.transactions.TransactionBuilder
10 |
11 | /**
12 | * A function that adds a list of output [AbstractToken] states to a [TransactionBuilder]. It automatically adds
13 | * [IssueTokenCommand] commands for each [IssuedTokenType]. A notary [Party] must be added to the [TransactionBuilder]
14 | * before this function can be called.
15 | */
16 | @Suspendable
17 | fun addIssueTokens(transactionBuilder: TransactionBuilder, outputs: List): TransactionBuilder {
18 | val outputGroups: Map> = outputs.groupBy { it.issuedTokenType }
19 | return transactionBuilder.apply {
20 | outputGroups.forEach { (issuedTokenType: IssuedTokenType, states: List) ->
21 | val issuers = states.map { it.issuer }.toSet()
22 | require(issuers.size == 1) { "All tokensToIssue must have the same issuer." }
23 | val issuer = issuers.single()
24 | var startingIndex = outputStates().size
25 | val indexesAdded = states.map { state ->
26 | addOutputState(state)
27 | startingIndex++
28 | }
29 | addCommand(IssueTokenCommand(issuedTokenType, indexesAdded), issuer.owningKey)
30 | }
31 | }
32 | }
33 |
34 | /**
35 | * A function that adds a list of output [AbstractToken] states to a [TransactionBuilder]. It automatically adds
36 | * [IssueTokenCommand] commands for each [IssuedTokenType]. A notary [Party] must be added to the [TransactionBuilder]
37 | * before this function can be called.
38 | */
39 | @Suspendable
40 | fun addIssueTokens(transactionBuilder: TransactionBuilder, vararg outputs: AbstractToken): TransactionBuilder {
41 | return addIssueTokens(transactionBuilder, outputs.toList())
42 | }
43 |
44 | /**
45 | * A function that adds a single output [AbstractToken] state to a [TransactionBuilder]. It automatically adds an
46 | * [IssueTokenCommand] command. A notary [Party] must be added to the [TransactionBuilder] before this function can be
47 | * called.
48 | */
49 | @Suspendable
50 | fun addIssueTokens(transactionBuilder: TransactionBuilder, output: AbstractToken): TransactionBuilder {
51 | return addIssueTokens(transactionBuilder, listOf(output))
52 | }
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/move/AbstractMoveTokensFlow.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.move
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.workflows.internal.flows.distribution.UpdateDistributionListFlow
5 | import com.r3.corda.lib.tokens.workflows.internal.flows.finality.ObserverAwareFinalityFlow
6 | import net.corda.core.flows.FinalityFlow
7 | import net.corda.core.flows.FlowLogic
8 | import net.corda.core.flows.FlowSession
9 | import net.corda.core.transactions.SignedTransaction
10 | import net.corda.core.transactions.TransactionBuilder
11 | import net.corda.core.utilities.ProgressTracker
12 |
13 | /**
14 | * An abstract class for the move tokens flows family.
15 | *
16 | * You must provide [participantSessions] and optional [observerSessions] for finalization. Override [addMove] to select
17 | * tokens to move. See helper functions in [MoveTokensUtilities] module.
18 | *
19 | * The flow performs basic tasks, generates move transaction proposal for all the participants, collects signatures and
20 | * finalises transaction with observers if present.
21 | *
22 | * @property participantSessions a list of flow participantSessions for the transaction participants.
23 | * @property observerSessions a list of flow participantSessions for the transaction observers.
24 | */
25 | abstract class AbstractMoveTokensFlow : FlowLogic() {
26 | abstract val participantSessions: List
27 | abstract val observerSessions: List
28 |
29 | companion object {
30 | object GENERATE : ProgressTracker.Step("Generating tokens to move.")
31 | object RECORDING : ProgressTracker.Step("Recording signed transaction.") {
32 | override fun childProgressTracker() = FinalityFlow.tracker()
33 | }
34 |
35 | object UPDATING : ProgressTracker.Step("Updating data distribution list.")
36 |
37 | fun tracker() = ProgressTracker(GENERATE, RECORDING, UPDATING)
38 | }
39 |
40 | override val progressTracker: ProgressTracker = tracker()
41 |
42 | /**
43 | * Adds a move of tokens to the [transactionBuilder]. This function mutates the builder.
44 | */
45 | @Suspendable
46 | abstract fun addMove(transactionBuilder: TransactionBuilder)
47 |
48 | @Suspendable
49 | override fun call(): SignedTransaction {
50 | // Initialise the transaction builder with no notary.
51 | val transactionBuilder = TransactionBuilder()
52 | progressTracker.currentStep = GENERATE
53 | // Add all the specified inputs and outputs to the transaction.
54 | // The correct commands and signing keys are also added.
55 | addMove(transactionBuilder)
56 | progressTracker.currentStep = RECORDING
57 | // Create new participantSessions if this is started as a top level flow.
58 | val signedTransaction = subFlow(
59 | ObserverAwareFinalityFlow(
60 | transactionBuilder = transactionBuilder,
61 | allSessions = participantSessions + observerSessions
62 | )
63 | )
64 | progressTracker.currentStep = UPDATING
65 | // Update the distribution list.
66 | subFlow(UpdateDistributionListFlow(signedTransaction))
67 | // Return the newly created transaction.
68 | return signedTransaction
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/move/ConfidentialMoveFungibleTokensFlow.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.move
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.contracts.types.TokenType
5 | import com.r3.corda.lib.tokens.selection.TokenQueryBy
6 | import com.r3.corda.lib.tokens.selection.database.selector.DatabaseTokenSelection
7 | import com.r3.corda.lib.tokens.workflows.flows.confidential.ConfidentialTokensFlow
8 | import com.r3.corda.lib.tokens.workflows.internal.flows.finality.TransactionRole
9 | import com.r3.corda.lib.tokens.workflows.types.PartyAndAmount
10 | import com.r3.corda.lib.tokens.workflows.types.toPairs
11 | import net.corda.core.flows.FlowLogic
12 | import net.corda.core.flows.FlowSession
13 | import net.corda.core.identity.AbstractParty
14 | import net.corda.core.node.services.vault.QueryCriteria
15 | import net.corda.core.transactions.SignedTransaction
16 |
17 | /**
18 | * Version of [MoveFungibleTokensFlow] using confidential identities. Confidential identities are generated and
19 | * exchanged for all parties that receive tokens states.
20 | *
21 | * Call this for one [TokenType] at a time. If you need to do multiple token types in one transaction then create a new
22 | * flow, calling [addMoveNonFungibleTokens] for each token type and handle confidential identities exchange yourself.
23 | *
24 | * @param partiesAndAmounts list of pairing party - amount of token that is to be moved to that party
25 | * @param participantSessions sessions with the participants of move transaction
26 | * @param changeHolder holder of the change outputs, it can be confidential identity
27 | * @param observerSessions optional sessions with the observer nodes, to witch the transaction will be broadcasted
28 | * @param queryCriteria additional criteria for token selection
29 | */
30 | class ConfidentialMoveFungibleTokensFlow
31 | @JvmOverloads
32 | constructor(
33 | val partiesAndAmounts: List>,
34 | val participantSessions: List,
35 | val changeHolder: AbstractParty,
36 | val observerSessions: List = emptyList(),
37 | val queryCriteria: QueryCriteria? = null
38 | ) : FlowLogic() {
39 |
40 | @JvmOverloads
41 | constructor(
42 | partyAndAmount: PartyAndAmount,
43 | participantSessions: List,
44 | changeHolder: AbstractParty,
45 | queryCriteria: QueryCriteria? = null,
46 | observerSessions: List = emptyList()
47 |
48 | ) : this(listOf(partyAndAmount), participantSessions, changeHolder, observerSessions, queryCriteria)
49 |
50 | @Suspendable
51 | override fun call(): SignedTransaction {
52 | // TODO add in memory selection too
53 | val tokenSelection = DatabaseTokenSelection(serviceHub)
54 | val (inputs, outputs) = tokenSelection.generateMove(
55 | lockId = stateMachine.id.uuid,
56 | partiesAndAmounts = partiesAndAmounts.toPairs(),
57 | changeHolder = changeHolder,
58 | queryBy = TokenQueryBy(queryCriteria = queryCriteria)
59 | )
60 | // TODO Not pretty fix, because we decided to go with sessions approach, we need to make sure that right responders are started depending on observer/participant role
61 | participantSessions.forEach { it.send(TransactionRole.PARTICIPANT) }
62 | observerSessions.forEach { it.send(TransactionRole.OBSERVER) }
63 | val confidentialOutputs = subFlow(ConfidentialTokensFlow(outputs, participantSessions))
64 | return subFlow(MoveTokensFlow(inputs, confidentialOutputs, participantSessions, observerSessions))
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/move/ConfidentialMoveNonFungibleTokensFlow.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.move
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.contracts.types.TokenType
5 | import com.r3.corda.lib.tokens.workflows.flows.confidential.ConfidentialTokensFlow
6 | import com.r3.corda.lib.tokens.workflows.internal.flows.finality.TransactionRole
7 | import com.r3.corda.lib.tokens.workflows.internal.selection.generateMoveNonFungible
8 | import com.r3.corda.lib.tokens.workflows.types.PartyAndToken
9 | import net.corda.core.flows.FlowLogic
10 | import net.corda.core.flows.FlowSession
11 | import net.corda.core.node.services.vault.QueryCriteria
12 | import net.corda.core.transactions.SignedTransaction
13 |
14 | /**
15 | * Version of [MoveNonFungibleTokensFlow] using confidential identities. Confidential identities are generated and
16 | * exchanged for all parties that receive tokens states.
17 | *
18 | * Call this for one [TokenType] at a time. If you need to do multiple token types in one transaction then create a new
19 | * flow, calling [addMoveFungibleTokens] for each token type and handle confidential identities exchange yourself.
20 | *
21 | * @param partyAndToken list of pairing party - token that is to be moved to that party
22 | * @param participantSessions sessions with the participants of move transaction
23 | * @param observerSessions optional sessions with the observer nodes, to witch the transaction will be broadcasted
24 | * @param queryCriteria additional criteria for token selection
25 | */
26 | class ConfidentialMoveNonFungibleTokensFlow
27 | @JvmOverloads
28 | constructor(
29 | val partyAndToken: PartyAndToken,
30 | val participantSessions: List,
31 | val observerSessions: List = emptyList(),
32 | val queryCriteria: QueryCriteria? = null
33 | ) : FlowLogic() {
34 | @Suspendable
35 | override fun call(): SignedTransaction {
36 | val (input, output) = generateMoveNonFungible(partyAndToken, serviceHub.vaultService, queryCriteria)
37 | // TODO Not pretty fix, because we decided to go with sessions approach, we need to make sure that right responders are started depending on observer/participant role
38 | participantSessions.forEach { it.send(TransactionRole.PARTICIPANT) }
39 | observerSessions.forEach { it.send(TransactionRole.OBSERVER) }
40 | val confidentialOutput = subFlow(ConfidentialTokensFlow(listOf(output), participantSessions)).single()
41 | return subFlow(MoveTokensFlow(input, confidentialOutput, participantSessions, observerSessions))
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/move/ConfidentialMoveTokensFlowHandler.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.move
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.workflows.flows.confidential.ConfidentialTokensFlowHandler
5 | import com.r3.corda.lib.tokens.workflows.internal.flows.finality.ObserverAwareFinalityFlowHandler
6 | import com.r3.corda.lib.tokens.workflows.internal.flows.finality.TransactionRole
7 | import net.corda.core.flows.FlowLogic
8 | import net.corda.core.flows.FlowSession
9 | import net.corda.core.utilities.unwrap
10 |
11 | /**
12 | * Responder flow to confidential move tokens flows: [ConfidentialMoveNonFungibleTokensFlow] and
13 | * [ConfidentialMoveFungibleTokensFlow].
14 | */
15 | class ConfidentialMoveTokensFlowHandler(val otherSession: FlowSession) : FlowLogic() {
16 | @Suspendable
17 | override fun call() {
18 | val role = otherSession.receive().unwrap { it }
19 | if (role == TransactionRole.PARTICIPANT) {
20 | subFlow(ConfidentialTokensFlowHandler(otherSession))
21 | }
22 | subFlow(ObserverAwareFinalityFlowHandler(otherSession))
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/move/MoveFungibleTokensFlow.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.move
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.contracts.types.TokenType
5 | import com.r3.corda.lib.tokens.workflows.types.PartyAndAmount
6 | import net.corda.core.flows.FlowSession
7 | import net.corda.core.identity.AbstractParty
8 | import net.corda.core.node.services.vault.QueryCriteria
9 | import net.corda.core.transactions.TransactionBuilder
10 |
11 | /**
12 | * Inlined flow used to move amounts of tokens to parties, [partiesAndAmounts] specifies what amount of tokens is moved
13 | * to each participant with possible change output paid to the [changeOwner].
14 | *
15 | * Call this for one [TokenType] at a time. If you need to do multiple token types in one transaction then create a new
16 | * flow, calling [addMoveFungibleTokens] for each token type.
17 | *
18 | * @param partiesAndAmounts list of pairing party - amount of token that is to be moved to that party
19 | * @param participantSessions sessions with the participants of move transaction
20 | * @param observerSessions optional sessions with the observer nodes, to witch the transaction will be broadcasted
21 | * @param queryCriteria additional criteria for token selection
22 | * @param changeHolder optional holder of the change outputs, it can be confidential identity, if not specified it
23 | * defaults to caller's legal identity
24 | */
25 | class MoveFungibleTokensFlow
26 | @JvmOverloads
27 | constructor(
28 | val partiesAndAmounts: List>,
29 | override val participantSessions: List,
30 | override val observerSessions: List = emptyList(),
31 | val queryCriteria: QueryCriteria? = null,
32 | val changeHolder: AbstractParty? = null
33 | ) : AbstractMoveTokensFlow() {
34 |
35 | @JvmOverloads
36 | constructor(
37 | partyAndAmount: PartyAndAmount,
38 | queryCriteria: QueryCriteria? = null,
39 | participantSessions: List,
40 | observerSessions: List = emptyList(),
41 | changeHolder: AbstractParty? = null
42 | ) : this(listOf(partyAndAmount), participantSessions, observerSessions, queryCriteria, changeHolder)
43 |
44 | @Suspendable
45 | override fun addMove(transactionBuilder: TransactionBuilder) {
46 | addMoveFungibleTokens(
47 | transactionBuilder = transactionBuilder,
48 | serviceHub = serviceHub,
49 | partiesAndAmounts = partiesAndAmounts,
50 | changeHolder = changeHolder ?: ourIdentity,
51 | queryCriteria = queryCriteria
52 | )
53 | }
54 | }
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/move/MoveNonFungibleTokensFlow.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.move
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.contracts.types.TokenType
5 | import com.r3.corda.lib.tokens.workflows.types.PartyAndToken
6 | import net.corda.core.flows.FlowSession
7 | import net.corda.core.node.services.vault.QueryCriteria
8 | import net.corda.core.transactions.TransactionBuilder
9 |
10 | /**
11 | * Inlined flow used to move non fungible tokens to parties, [partiesAndTokens] specifies what tokens are moved
12 | * to each participant.
13 | *
14 | * Call this for one [TokenType] at a time. If you need to do multiple token types in one transaction then create a new
15 | * flow, calling [addMoveNonFungibleTokens] for each token type.
16 | *
17 | * @param partyAndToken pairing party - token that is to be moved to that party
18 | * @param participantSessions sessions with the participants of move transaction
19 | * @param observerSessions optional sessions with the observer nodes, to witch the transaction will be broadcasted
20 | * @param queryCriteria additional criteria for token selection
21 | */
22 | class MoveNonFungibleTokensFlow
23 | @JvmOverloads
24 | constructor(
25 | val partyAndToken: PartyAndToken,
26 | override val participantSessions: List,
27 | override val observerSessions: List = emptyList(),
28 | val queryCriteria: QueryCriteria?
29 | ) : AbstractMoveTokensFlow() {
30 | @Suspendable
31 | override fun addMove(transactionBuilder: TransactionBuilder) {
32 | addMoveNonFungibleTokens(transactionBuilder, serviceHub, partyAndToken, queryCriteria)
33 | }
34 | }
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/move/MoveTokensFlow.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.move
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.contracts.states.AbstractToken
5 | import com.r3.corda.lib.tokens.contracts.types.TokenType
6 | import net.corda.core.contracts.StateAndRef
7 | import net.corda.core.flows.FlowSession
8 | import net.corda.core.transactions.TransactionBuilder
9 |
10 | /**
11 | * General inlined flow used to move any type of tokens. This flow builds a transaction containing passed as parameters
12 | * input and output states, but all checks should have be done before calling this flow as a subflow.
13 | *
14 | * It can only be called for one [TokenType] at a time. If you need to do multiple token types in one transaction then
15 | * create a new flow, calling [addMoveTokens] for each token type.
16 | *
17 | * @param inputs list of token inputs to move
18 | * @param outputs list of result token outputs
19 | * @param participantSessions session with the participants of move tokens transaction
20 | * @param observerSessions session with optional observers of the redeem transaction
21 | */
22 | class MoveTokensFlow
23 | @JvmOverloads
24 | constructor(
25 | val inputs: List>,
26 | val outputs: List,
27 | override val participantSessions: List,
28 | override val observerSessions: List = emptyList()
29 | ) : AbstractMoveTokensFlow() {
30 | @JvmOverloads
31 | constructor(
32 | input: StateAndRef,
33 | output: AbstractToken,
34 | participantSessions: List,
35 | observerSessions: List = emptyList()
36 | ) : this(listOf(input), listOf(output), participantSessions, observerSessions)
37 |
38 | @Suspendable
39 | override fun addMove(transactionBuilder: TransactionBuilder) {
40 | addMoveTokens(transactionBuilder, inputs, outputs)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/move/MoveTokensFlowHandler.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.move
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.workflows.internal.flows.finality.ObserverAwareFinalityFlowHandler
5 | import net.corda.core.flows.FlowLogic
6 | import net.corda.core.flows.FlowSession
7 |
8 | /**
9 | * Responder flow for [MoveTokensFlow], [MoveFungibleTokensFlow], [MoveNonFungibleTokensFlow]
10 | */
11 | class MoveTokensFlowHandler(val otherSession: FlowSession) : FlowLogic() {
12 | @Suspendable
13 | override fun call() {
14 | // Resolve the move transaction.
15 | if (!serviceHub.myInfo.isLegalIdentity(otherSession.counterparty)) {
16 | subFlow(ObserverAwareFinalityFlowHandler(otherSession))
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/redeem/AbstractRedeemTokensFlow.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.redeem
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.ci.workflows.SyncKeyMappingFlow
5 | import com.r3.corda.lib.tokens.workflows.internal.flows.finality.ObserverAwareFinalityFlow
6 | import com.r3.corda.lib.tokens.workflows.internal.flows.finality.TransactionRole
7 | import com.r3.corda.lib.tokens.workflows.utilities.ourSigningKeys
8 | import net.corda.core.flows.CollectSignaturesFlow
9 | import net.corda.core.flows.FlowLogic
10 | import net.corda.core.flows.FlowSession
11 | import net.corda.core.transactions.SignedTransaction
12 | import net.corda.core.transactions.TransactionBuilder
13 | import net.corda.core.utilities.ProgressTracker
14 |
15 | /**
16 | * Abstract class for the redeem token flows family.
17 | * You must provide [issuerSession] and optional [observerSessions] for finalization. Override [generateExit] to select
18 | * tokens for redeeming.
19 | * The flow performs basic tasks, generates redeem transaction proposal for the issuer, synchronises any confidential
20 | * identities from the states to redeem with the issuer (bear in mind that issuer usually isn't involved in move of tokens),
21 | * collects signatures and finalises transaction with observers if present.
22 | */
23 | abstract class AbstractRedeemTokensFlow : FlowLogic() {
24 |
25 | abstract val issuerSession: FlowSession
26 | abstract val observerSessions: List
27 |
28 | companion object {
29 | object SELECTING_STATES : ProgressTracker.Step("Selecting states to redeem.")
30 | object SYNC_IDS : ProgressTracker.Step("Synchronising confidential identities.")
31 | object COLLECT_SIGS : ProgressTracker.Step("Collecting signatures")
32 | object FINALISING_TX : ProgressTracker.Step("Finalising transaction")
33 |
34 | fun tracker() = ProgressTracker(SELECTING_STATES, SYNC_IDS, COLLECT_SIGS, FINALISING_TX)
35 | }
36 |
37 | override val progressTracker: ProgressTracker = tracker()
38 |
39 | /**
40 | * Add redeem of tokens to the [transactionBuilder]. Modifies builder.
41 | */
42 | @Suspendable
43 | abstract fun generateExit(transactionBuilder: TransactionBuilder)
44 |
45 | @Suspendable
46 | override fun call(): SignedTransaction {
47 | issuerSession.send(TransactionRole.PARTICIPANT)
48 | observerSessions.forEach { it.send(TransactionRole.OBSERVER) }
49 | val txBuilder = TransactionBuilder()
50 | progressTracker.currentStep = SELECTING_STATES
51 | generateExit(txBuilder)
52 | // First synchronise identities between issuer and our states.
53 | // TODO: Only do this if necessary.
54 | progressTracker.currentStep = SYNC_IDS
55 | subFlow(SyncKeyMappingFlow(issuerSession, txBuilder.toWireTransaction(serviceHub)))
56 | val ourSigningKeys = txBuilder.toLedgerTransaction(serviceHub).ourSigningKeys(serviceHub)
57 | val partialStx = serviceHub.signInitialTransaction(txBuilder, ourSigningKeys)
58 | // Call collect signatures flow, issuer should perform all the checks for redeeming states.
59 | progressTracker.currentStep = COLLECT_SIGS
60 | val stx = subFlow(CollectSignaturesFlow(partialStx, listOf(issuerSession), ourSigningKeys))
61 | progressTracker.currentStep = FINALISING_TX
62 | return subFlow(ObserverAwareFinalityFlow(stx, observerSessions + issuerSession))
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/redeem/ConfidentialRedeemFungibleTokensFlow.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.redeem
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.contracts.types.TokenType
5 | import net.corda.core.contracts.Amount
6 | import net.corda.core.flows.FlowLogic
7 | import net.corda.core.flows.FlowSession
8 | import net.corda.core.identity.AbstractParty
9 | import net.corda.core.identity.AnonymousParty
10 | import net.corda.core.node.services.vault.QueryCriteria
11 | import net.corda.core.transactions.SignedTransaction
12 |
13 | /**
14 | * Version of [RedeemFungibleTokensFlow] using confidential identity for a change owner.
15 | * There is no [NonFungibleToken] version of this flow, because there is no output paid.
16 | * Identities are synchronised during normal redeem call.
17 | *
18 | * @param amount amount of token to redeem
19 | * @param issuerSession session with the issuer tokens should be redeemed with
20 | * @param observerSessions optional sessions with the observer nodes, to witch the transaction will be broadcasted
21 | * @param additionalQueryCriteria additional criteria for token selection
22 | * @param changeHolder optional change key, if using accounts you should generate the change key prior to calling this
23 | * flow then pass it in to the flow via this parameter
24 | */
25 | class ConfidentialRedeemFungibleTokensFlow
26 | @JvmOverloads
27 | constructor(
28 | val amount: Amount,
29 | val issuerSession: FlowSession,
30 | val observerSessions: List = emptyList(),
31 | val additionalQueryCriteria: QueryCriteria? = null,
32 | val changeHolder: AbstractParty? = null
33 | ) : FlowLogic() {
34 | @Suspendable
35 | override fun call(): SignedTransaction {
36 | // If a change holder key is not specified then one will be created for you. NB. If you want to use accounts
37 | // with tokens, then you must generate and allocate the key to an account up-front and pass the key in as the
38 | // "changeHolder".
39 | val confidentialHolder = changeHolder ?: let {
40 | val key = serviceHub.keyManagementService.freshKey()
41 | AnonymousParty(key)
42 | }
43 | return subFlow(RedeemFungibleTokensFlow(
44 | amount = amount,
45 | issuerSession = issuerSession,
46 | changeHolder = confidentialHolder, // This will never be null.
47 | observerSessions = observerSessions,
48 | additionalQueryCriteria = additionalQueryCriteria
49 | ))
50 | }
51 | }
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/redeem/ConfidentialRedeemFungibleTokensFlowHandler.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.redeem
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import net.corda.core.flows.FlowLogic
5 | import net.corda.core.flows.FlowSession
6 |
7 | /**
8 | * Responder flow to [ConfidentialRedeemFungibleTokensFlow].
9 | */
10 | class ConfidentialRedeemFungibleTokensFlowHandler(val otherSession: FlowSession) : FlowLogic() {
11 | @Suspendable
12 | override fun call() {
13 | subFlow(RedeemTokensFlowHandler(otherSession))
14 | }
15 | }
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/redeem/RedeemFungibleTokensFlow.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.redeem
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.contracts.types.TokenType
5 | import net.corda.core.contracts.Amount
6 | import net.corda.core.flows.FlowSession
7 | import net.corda.core.identity.AbstractParty
8 | import net.corda.core.node.services.vault.QueryCriteria
9 | import net.corda.core.transactions.TransactionBuilder
10 |
11 | /**
12 | * Inlined flow used to redeem amount of [FungibleToken]s issued by the particular issuer with possible change output
13 | * paid to the [changeHolder].
14 | *
15 | * @param amount amount of token to redeem
16 | * @param changeHolder owner of possible change output, which defaults to the node identity of the calling node
17 | * @param issuerSession session with the issuer tokens should be redeemed with
18 | * @param observerSessions optional sessions with the observer nodes, to witch the transaction will be broadcasted
19 | * @param additionalQueryCriteria additional criteria for token selection
20 | */
21 | class RedeemFungibleTokensFlow
22 | @JvmOverloads
23 | constructor(
24 | val amount: Amount,
25 | override val issuerSession: FlowSession,
26 | val changeHolder: AbstractParty? = null,
27 | override val observerSessions: List = emptyList(),
28 | val additionalQueryCriteria: QueryCriteria? = null
29 | ) : AbstractRedeemTokensFlow() {
30 | @Suspendable
31 | override fun generateExit(transactionBuilder: TransactionBuilder) {
32 | addFungibleTokensToRedeem(
33 | transactionBuilder = transactionBuilder,
34 | serviceHub = serviceHub,
35 | amount = amount,
36 | issuer = issuerSession.counterparty,
37 | changeHolder = changeHolder ?: ourIdentity,
38 | additionalQueryCriteria = additionalQueryCriteria
39 | )
40 | }
41 | }
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/redeem/RedeemNonFungibleTokensFlow.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.redeem
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.contracts.types.TokenType
5 | import net.corda.core.flows.FlowSession
6 | import net.corda.core.transactions.TransactionBuilder
7 |
8 | /**
9 | * Inlined flow used to redeem [NonFungibleToken] [heldToken] issued by the particular issuer.
10 | *
11 | * @param heldToken non fungible token to redeem
12 | * @param issuerSession session with the issuer token should be redeemed with
13 | * @param observerSessions optional sessions with the observer nodes, to witch the transaction will be broadcasted
14 | */
15 | class RedeemNonFungibleTokensFlow(
16 | val heldToken: TokenType,
17 | override val issuerSession: FlowSession,
18 | override val observerSessions: List
19 | ) : AbstractRedeemTokensFlow() {
20 | @Suspendable
21 | override fun generateExit(transactionBuilder: TransactionBuilder) {
22 | addNonFungibleTokensToRedeem(
23 | transactionBuilder = transactionBuilder,
24 | serviceHub = serviceHub,
25 | heldToken = heldToken,
26 | issuer = issuerSession.counterparty
27 | )
28 | }
29 | }
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/redeem/RedeemTokensFlow.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.redeem
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.contracts.states.AbstractToken
5 | import com.r3.corda.lib.tokens.contracts.types.TokenType
6 | import net.corda.core.contracts.StateAndRef
7 | import net.corda.core.flows.FlowSession
8 | import net.corda.core.transactions.TransactionBuilder
9 |
10 | /**
11 | * General inlined flow used to redeem any type of tokens with the issuer. Should be called on tokens' owner side.
12 | * Notice that token selection and change output generation should be done beforehand. This flow builds a transaction
13 | * containing those states, but all checks should have be done before calling this flow as a subflow.
14 | * It can only be called for one [TokenType] at a time. If you need to do multiple token types in one transaction then create a new
15 | * flow, calling [addTokensToRedeem] for each token type.
16 | *
17 | * @param inputs list of token inputs to redeem
18 | * @param changeOutput possible change output to be paid back to the tokens owner
19 | * @param issuerSession session with the issuer of the tokens
20 | * @param observerSessions session with optional observers of the redeem transaction
21 | */
22 | // Called on owner side.
23 | class RedeemTokensFlow
24 | @JvmOverloads
25 | constructor(
26 | val inputs: List>,
27 | val changeOutput: AbstractToken?,
28 | override val issuerSession: FlowSession,
29 | override val observerSessions: List = emptyList()
30 | ) : AbstractRedeemTokensFlow() {
31 | @Suspendable
32 | override fun generateExit(transactionBuilder: TransactionBuilder) {
33 | addTokensToRedeem(transactionBuilder, inputs, changeOutput)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/redeem/RedeemTokensFlowHandler.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.redeem
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.ci.workflows.SyncKeyMappingFlowHandler
5 | import com.r3.corda.lib.tokens.contracts.states.AbstractToken
6 | import com.r3.corda.lib.tokens.workflows.internal.checkOwner
7 | import com.r3.corda.lib.tokens.workflows.internal.checkSameIssuer
8 | import com.r3.corda.lib.tokens.workflows.internal.checkSameNotary
9 | import com.r3.corda.lib.tokens.workflows.internal.flows.finality.ObserverAwareFinalityFlowHandler
10 | import com.r3.corda.lib.tokens.workflows.internal.flows.finality.TransactionRole
11 | import net.corda.core.crypto.SecureHash
12 | import net.corda.core.flows.FlowLogic
13 | import net.corda.core.flows.FlowSession
14 | import net.corda.core.flows.SignTransactionFlow
15 | import net.corda.core.transactions.SignedTransaction
16 | import net.corda.core.utilities.unwrap
17 |
18 | /**
19 | * Inlined responder flow called on the issuer side, should be used with: [RedeemFungibleTokensFlow],
20 | * [RedeemNonFungibleTokensFlow], [RedeemTokensFlow].
21 | */
22 | // Called on Issuer side.
23 | class RedeemTokensFlowHandler(val otherSession: FlowSession) : FlowLogic() {
24 | @Suspendable
25 | override fun call(): SignedTransaction? {
26 | var expectedTransactionId: SecureHash? = null
27 | val role = otherSession.receive().unwrap { it }
28 | if (role == TransactionRole.PARTICIPANT) {
29 | // Synchronise all confidential identities, issuer isn't involved in move transactions, so states holders may
30 | // not be known to this node.
31 | subFlow(SyncKeyMappingFlowHandler(otherSession))
32 | // There is edge case where issuer redeems with themselves, then we need to be careful not to call handler for
33 | // collect signatures for already fully signed transaction - it causes session messages mismatch.
34 | if (!serviceHub.myInfo.isLegalIdentity(otherSession.counterparty)) {
35 | // Perform all the checks to sign the transaction.
36 | subFlow(object : SignTransactionFlow(otherSession) {
37 | // TODO if it is with itself, then we won't perform that check...
38 | override fun checkTransaction(stx: SignedTransaction) {
39 | val stateAndRefsToRedeem = stx.toLedgerTransaction(serviceHub, false).inRefsOfType()
40 | checkSameIssuer(stateAndRefsToRedeem, ourIdentity)
41 | checkSameNotary(stateAndRefsToRedeem)
42 | checkOwner(serviceHub.identityService, stateAndRefsToRedeem, otherSession.counterparty)
43 | expectedTransactionId = stx.id
44 | }
45 | })
46 | }
47 | }
48 | return if (!serviceHub.myInfo.isLegalIdentity(otherSession.counterparty)) {
49 | // Call observer aware finality flow handler.
50 | subFlow(ObserverAwareFinalityFlowHandler(otherSession, expectedTransactionId))
51 | } else null
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/flows/rpc/IssueTokens.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.flows.rpc
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.tokens.contracts.states.AbstractToken
5 | import com.r3.corda.lib.tokens.workflows.flows.issue.ConfidentialIssueTokensFlow
6 | import com.r3.corda.lib.tokens.workflows.flows.issue.ConfidentialIssueTokensFlowHandler
7 | import com.r3.corda.lib.tokens.workflows.flows.issue.IssueTokensFlow
8 | import com.r3.corda.lib.tokens.workflows.flows.issue.IssueTokensFlowHandler
9 | import com.r3.corda.lib.tokens.workflows.utilities.sessionsForParticipants
10 | import com.r3.corda.lib.tokens.workflows.utilities.sessionsForParties
11 | import net.corda.core.flows.*
12 | import net.corda.core.identity.Party
13 | import net.corda.core.transactions.SignedTransaction
14 |
15 | /**
16 | * A flow for issuing fungible or non-fungible tokens which initiates its own participantSessions. This is the case when
17 | * called from the node rpc or in a unit test. However, in the case where you already have a session with another [Party]
18 | * and you wish to issue tokens as part of a wider workflow, then use [IssueTokensFlow].
19 | *
20 | * @property tokensToIssue a list of [AbstractToken]s to issue
21 | * @property observers a set of observing [Party]s
22 | */
23 | @StartableByService
24 | @StartableByRPC
25 | @InitiatingFlow
26 | class IssueTokens
27 | @JvmOverloads
28 | constructor(
29 | val tokensToIssue: List,
30 | val observers: List = emptyList()
31 | ) : FlowLogic() {
32 |
33 | @Suspendable
34 | override fun call(): SignedTransaction {
35 | val observerSessions = sessionsForParties(observers)
36 | val participantSessions = sessionsForParticipants(tokensToIssue)
37 | return subFlow(IssueTokensFlow(tokensToIssue, participantSessions, observerSessions))
38 | }
39 | }
40 |
41 | /**
42 | * Responder flow for [IssueTokens].
43 | */
44 | @InitiatedBy(IssueTokens::class)
45 | class IssueTokensHandler(val otherSession: FlowSession) : FlowLogic() {
46 | @Suspendable
47 | override fun call() = subFlow(IssueTokensFlowHandler(otherSession))
48 | }
49 |
50 | /**
51 | * A flow for issuing fungible or non-fungible tokens which initiates its own participantSessions. This is the case when called
52 | * from the node rpc or in a unit test. However, in the case where you already have a session with another [Party] and
53 | * you wish to issue tokens as part of a wider workflow, then use [IssueTokensFlow].
54 | *
55 | * @property tokensToIssue a list of [AbstractToken]s to issue
56 | * @property observers aset of observing [Party]s
57 | */
58 | @StartableByService
59 | @StartableByRPC
60 | @InitiatingFlow
61 | class ConfidentialIssueTokens
62 | @JvmOverloads
63 | constructor(
64 | val tokensToIssue: List,
65 | val observers: List = emptyList()
66 | ) : FlowLogic() {
67 | @Suspendable
68 | override fun call(): SignedTransaction {
69 | val observerSessions = sessionsForParties(observers)
70 | val participantSessions = sessionsForParticipants(tokensToIssue)
71 | return subFlow(ConfidentialIssueTokensFlow(tokensToIssue, participantSessions, observerSessions))
72 | }
73 | }
74 |
75 | /**
76 | * Responder flow for [ConfidentialIssueTokens].
77 | */
78 | @InitiatedBy(ConfidentialIssueTokens::class)
79 | class ConfidentialIssueTokensHandler(val otherSession: FlowSession) : FlowLogic() {
80 | @Suspendable
81 | override fun call() = subFlow(ConfidentialIssueTokensFlowHandler(otherSession))
82 | }
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/internal/CheckUtilities.kt:
--------------------------------------------------------------------------------
1 | @file:JvmName("CheckUtilities")
2 | package com.r3.corda.lib.tokens.workflows.internal
3 |
4 | import co.paralleluniverse.fibers.Suspendable
5 | import com.r3.corda.lib.tokens.contracts.states.AbstractToken
6 | import net.corda.core.contracts.StateAndRef
7 | import net.corda.core.identity.Party
8 | import net.corda.core.node.services.IdentityService
9 |
10 | // Check that all states share the same notary.
11 | @Suspendable
12 | internal fun checkSameNotary(stateAndRefs: List>) {
13 | val notary = stateAndRefs.first().state.notary
14 | check(stateAndRefs.all { it.state.notary == notary }) {
15 | "All states should have the same notary. Automatic notary change isn't supported for now."
16 | }
17 | }
18 |
19 | // Checks if all states have the same issuer. If the issuer is provided as a parameter then it checks if all states
20 | // were issued by this issuer.
21 | @Suspendable
22 | internal fun checkSameIssuer(
23 | stateAndRefs: List>,
24 | issuer: Party? = null
25 | ) {
26 | val issuerToCheck = issuer ?: stateAndRefs.first().state.data.issuer
27 | check(stateAndRefs.all { it.state.data.issuer == issuerToCheck }) {
28 | "Tokens with different issuers."
29 | }
30 | }
31 |
32 | // Check if owner of the states is well known. Check if states come from the same owner.
33 | // Should be called after synchronising identities step.
34 | @Suspendable
35 | internal fun checkOwner(
36 | identityService: IdentityService,
37 | stateAndRefs: List>,
38 | counterparty: Party
39 | ) {
40 | val owners = stateAndRefs.map { identityService.wellKnownPartyFromAnonymous(it.state.data.holder) }
41 | check(owners.all { it != null }) {
42 | "Received states with owner that we don't know about."
43 | }
44 | check(owners.all { it == counterparty }) {
45 | "Received states that don't come from counterparty that initiated the flow."
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/internal/README.md:
--------------------------------------------------------------------------------
1 | **NOTE:** These packages are internal only for the time being, which means
2 | no backwards compatibility is guaranteed. Use at your own risk.
--------------------------------------------------------------------------------
/workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/internal/flows/confidential/AnonymisePartiesFlow.kt:
--------------------------------------------------------------------------------
1 | package com.r3.corda.lib.tokens.workflows.internal.flows.confidential
2 |
3 | import co.paralleluniverse.fibers.Suspendable
4 | import com.r3.corda.lib.ci.workflows.RequestKeyFlow
5 | import net.corda.core.flows.FlowLogic
6 | import net.corda.core.flows.FlowSession
7 | import net.corda.core.identity.AnonymousParty
8 | import net.corda.core.identity.Party
9 |
10 | /**
11 | * This flow notifies prospective token holders that they must generate a new key pair. As this is an in-line sub-flow,
12 | * we must pass it a list of sessions, which _may_ contain sessions for observers. As such, only the parties that need
13 | * to generate a new key are sent a [ActionRequest.CREATE_NEW_KEY] notification and everyone else is sent
14 | * [ActionRequest.DO_NOTHING].
15 | */
16 | class AnonymisePartiesFlow(
17 | val parties: List,
18 | val sessions: List
19 | ) : FlowLogic