├── .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 | 18 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Debug_CorDapp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Run_Contract_Tests.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | true 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Run_Flow_Tests.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | true 19 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Run_Integration_Tests.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | true 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Run_Template_Client.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Run_Template_Cordapp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Run_Template_Server.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Unit_tests.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 17 | 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>() { 20 | @Suspendable 21 | override fun call(): Map { 22 | val sessionParties = sessions.map(FlowSession::counterparty) 23 | val partiesWithoutASession = parties.minus(sessionParties) 24 | require(partiesWithoutASession.isEmpty()) { "You must provide sessions for all parties. " + 25 | "No sessions provided for parties: $partiesWithoutASession" } 26 | return sessions.mapNotNull { session -> 27 | val party = session.counterparty 28 | if (party in parties) { 29 | session.send(ActionRequest.CREATE_NEW_KEY) 30 | val anonParty = subFlow(RequestKeyFlow(session)) 31 | Pair(party, anonParty) 32 | } else { 33 | session.send(ActionRequest.DO_NOTHING) 34 | null 35 | } 36 | }.toMap() 37 | } 38 | } -------------------------------------------------------------------------------- /workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/internal/flows/confidential/AnonymisePartiesFlowHandler.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.ProvideKeyFlow 5 | import net.corda.core.flows.FlowLogic 6 | import net.corda.core.flows.FlowSession 7 | import net.corda.core.utilities.unwrap 8 | 9 | class AnonymisePartiesFlowHandler(val otherSession: FlowSession) : FlowLogic() { 10 | @Suspendable 11 | override fun call() { 12 | val action = otherSession.receive().unwrap { it } 13 | if (action == ActionRequest.CREATE_NEW_KEY) { 14 | subFlow(ProvideKeyFlow(otherSession)) 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/internal/flows/confidential/ConfidentialIdentityUtilities.kt: -------------------------------------------------------------------------------- 1 | package com.r3.corda.lib.tokens.workflows.internal.flows.confidential 2 | 3 | import net.corda.core.serialization.CordaSerializable 4 | 5 | @CordaSerializable 6 | enum class ActionRequest { DO_NOTHING, CREATE_NEW_KEY } 7 | 8 | -------------------------------------------------------------------------------- /workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/internal/flows/distribution/README.md: -------------------------------------------------------------------------------- 1 | **Note:** All this code will eventually be replaced by data distribution groups. -------------------------------------------------------------------------------- /workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/internal/flows/distribution/RequestAdditionToDistributionList.kt: -------------------------------------------------------------------------------- 1 | package com.r3.corda.lib.tokens.workflows.internal.flows.distribution 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.utilities.addPartyToDistributionList 6 | import net.corda.core.contracts.StateAndRef 7 | import net.corda.core.flows.FlowLogic 8 | import net.corda.core.flows.FlowSession 9 | import net.corda.core.flows.InitiatedBy 10 | import net.corda.core.flows.InitiatingFlow 11 | import net.corda.core.utilities.unwrap 12 | 13 | /** 14 | * Simple set of flows for a party to request updates for a particular evolvable token. These flows don't do much 15 | * checking, the responder always adds a requesting party to the distribution list. 16 | * 17 | * As this flow requires a [StateAndRef] in the constructor, it is only intended to be called by tokenHolders that have _at 18 | * least_ one version of the evolvable token. This is probably acceptable as when the issuer issues some of the token 19 | * for the first time to a party, then they will also send along the most current version of the evolvable token as 20 | * well. This also simplifies the workflow a bit; when tokensToIssue are issued, it is expected that the issuer sends along the 21 | * evolvable token as well and likewise when some amount of token is transferred from one party to another. Once a party 22 | * has at least one version of the evolvable token, they can request to be automatically updated using this flow going 23 | * forward. 24 | * 25 | * When data distribution groups are available then these flows can be retired. However, they are sufficient for the 26 | * time being. 27 | */ 28 | object RequestAdditionToDistributionList { 29 | 30 | sealed class FlowResult { 31 | object Success : FlowResult() 32 | // No failure for now! 33 | } 34 | 35 | @InitiatingFlow 36 | class Initiator(val stateAndRef: StateAndRef) : FlowLogic() { 37 | @Suspendable 38 | override fun call() { 39 | val state = stateAndRef.state.data 40 | // Pick the first maintainer. 41 | // TODO: Try each maintainer. 42 | val maintainer = state.maintainers.first() 43 | val session = initiateFlow(maintainer) 44 | logger.info("Requesting addition to $maintainer distribution list for ${state.linearId}.") 45 | val result = session.sendAndReceive(stateAndRef).unwrap { it } 46 | // Don't do anything with the flow result for now. 47 | return when (result) { 48 | FlowResult.Success -> Unit 49 | } 50 | } 51 | } 52 | 53 | @InitiatedBy(Initiator::class) 54 | class Responder(val otherSession: FlowSession) : FlowLogic() { 55 | @Suspendable 56 | override fun call() { 57 | // Receive the stateAndRef that the requesting party wants updates for. 58 | val stateAndRef = otherSession.receive>().unwrap { it } 59 | val linearId = stateAndRef.state.data.linearId 60 | logger.info("Receiving request from ${otherSession.counterparty} to be added to the distribution list for $linearId.") 61 | addPartyToDistributionList(otherSession.counterparty, linearId) 62 | otherSession.send(FlowResult.Success) 63 | } 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/internal/flows/distribution/UpdateDistributionListFlow.kt: -------------------------------------------------------------------------------- 1 | package com.r3.corda.lib.tokens.workflows.internal.flows.distribution 2 | 3 | import co.paralleluniverse.fibers.Suspendable 4 | import com.r3.corda.lib.tokens.contracts.commands.IssueTokenCommand 5 | import com.r3.corda.lib.tokens.contracts.commands.MoveTokenCommand 6 | import com.r3.corda.lib.tokens.contracts.states.AbstractToken 7 | import com.r3.corda.lib.tokens.contracts.types.TokenPointer 8 | import net.corda.core.contracts.Command 9 | import net.corda.core.contracts.TransactionState 10 | import net.corda.core.flows.FlowLogic 11 | import net.corda.core.flows.InitiatingFlow 12 | import net.corda.core.transactions.SignedTransaction 13 | import net.corda.core.utilities.ProgressTracker 14 | 15 | // TODO: Handle updates of the distribution list for observers. 16 | @InitiatingFlow 17 | class UpdateDistributionListFlow(val signedTransaction: SignedTransaction) : FlowLogic() { 18 | 19 | companion object { 20 | object ADD_DIST_LIST : ProgressTracker.Step("Adding to distribution list.") 21 | object UPDATE_DIST_LIST : ProgressTracker.Step("Updating distribution list.") 22 | 23 | fun tracker() = ProgressTracker(ADD_DIST_LIST, UPDATE_DIST_LIST) 24 | } 25 | 26 | override val progressTracker: ProgressTracker = tracker() 27 | 28 | @Suspendable 29 | override fun call() { 30 | val tx = signedTransaction.tx 31 | val tokensWithTokenPointers: List = tx.outputs 32 | .map(TransactionState<*>::data) 33 | .filterIsInstance() 34 | .filter { it.tokenType is TokenPointer<*> } // IntelliJ bug?? Check is not always true! 35 | // There are no evolvable tokens so we don't need to update any distribution lists. Otherwise, carry on. 36 | if (tokensWithTokenPointers.isEmpty()) return 37 | val issueCmds: List = tx.commands 38 | .map(Command<*>::value) 39 | .filterIsInstance() 40 | .filter { it.token.tokenType is TokenPointer<*> } 41 | val moveCmds: List = tx.commands 42 | .map(Command<*>::value) 43 | .filterIsInstance() 44 | .filter { it.token.tokenType is TokenPointer<*> } 45 | if (issueCmds.isNotEmpty()) { 46 | // If it's an issue transaction then the party calling this flow will be the issuer and they just need to 47 | // update their local distribution list with the parties that have been just issued tokens. 48 | val issueTypes: List> = issueCmds.map { it.token.tokenType }.mapNotNull { it as? TokenPointer<*> } 49 | progressTracker.currentStep = ADD_DIST_LIST 50 | val issueStates: List = tokensWithTokenPointers.filter { 51 | it.tokenType in issueTypes 52 | } 53 | addToDistributionList(issueStates) 54 | } 55 | if (moveCmds.isNotEmpty()) { 56 | // If it's a move then we need to call back to the issuer to update the distribution lists with the new 57 | // token holders. 58 | val moveTypes = moveCmds.map { it.token.tokenType } 59 | progressTracker.currentStep = UPDATE_DIST_LIST 60 | val moveStates = tokensWithTokenPointers.filter { it.tokenType in moveTypes } 61 | updateDistributionList(moveStates) 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/internal/flows/distribution/UpdateDistributionListFlowHandler.kt: -------------------------------------------------------------------------------- 1 | package com.r3.corda.lib.tokens.workflows.internal.flows.distribution 2 | 3 | import co.paralleluniverse.fibers.Suspendable 4 | import com.r3.corda.lib.tokens.workflows.utilities.addPartyToDistributionList 5 | import net.corda.core.flows.FlowLogic 6 | import net.corda.core.flows.FlowSession 7 | import net.corda.core.flows.InitiatedBy 8 | import net.corda.core.utilities.unwrap 9 | 10 | @InitiatedBy(UpdateDistributionListFlow::class) 11 | class UpdateDistributionListFlowHandler(val otherSession: FlowSession) : FlowLogic() { 12 | @Suspendable 13 | override fun call() { 14 | val distListUpdate = otherSession.receive().unwrap { 15 | // Check that the request comes from that party. 16 | check(it.sender == otherSession.counterparty) { 17 | "Got distribution list update request from a counterparty: ${otherSession.counterparty} " + 18 | "that isn't a signer of request: ${it.sender}." 19 | } 20 | it 21 | } 22 | // Check that receiver is well known party. 23 | serviceHub.identityService.requireWellKnownPartyFromAnonymous(distListUpdate.receiver) 24 | addPartyToDistributionList(distListUpdate.receiver, distListUpdate.linearId) 25 | } 26 | } -------------------------------------------------------------------------------- /workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/internal/flows/finality/FinalityUtilities.kt: -------------------------------------------------------------------------------- 1 | package com.r3.corda.lib.tokens.workflows.internal.flows.finality 2 | 3 | import co.paralleluniverse.fibers.Suspendable 4 | import com.r3.corda.lib.tokens.contracts.commands.RedeemTokenCommand 5 | import com.r3.corda.lib.tokens.workflows.utilities.participants 6 | import com.r3.corda.lib.tokens.workflows.utilities.toWellKnownParties 7 | import com.r3.corda.lib.tokens.workflows.utilities.toWellKnownPartiesExcludingUnknown 8 | import net.corda.core.contracts.CommandWithParties 9 | import net.corda.core.identity.Party 10 | import net.corda.core.node.ServiceHub 11 | import net.corda.core.serialization.CordaSerializable 12 | import net.corda.core.transactions.LedgerTransaction 13 | 14 | @CordaSerializable 15 | enum class TransactionRole { PARTICIPANT, OBSERVER } 16 | 17 | @Suspendable 18 | internal fun LedgerTransaction.getParticipantsAndIssuers(serviceHub: ServiceHub): Set { 19 | 20 | val issuers: Set = commands 21 | .map(CommandWithParties<*>::value) 22 | .filterIsInstance() 23 | .map { it.token.issuer } 24 | .toSet() 25 | 26 | return participants.toWellKnownParties(serviceHub).toSet() + issuers 27 | } 28 | 29 | @Suspendable 30 | internal fun LedgerTransaction.getParticipantsAndIssuersExcludingUnknown(serviceHub: ServiceHub): Set { 31 | 32 | val issuers: Set = commands 33 | .map(CommandWithParties<*>::value) 34 | .filterIsInstance() 35 | .map { it.token.issuer } 36 | .toSet() 37 | 38 | return participants.toWellKnownPartiesExcludingUnknown(serviceHub).toSet() + issuers 39 | } -------------------------------------------------------------------------------- /workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/internal/flows/finality/ObserverAwareFinalityFlowHandler.kt: -------------------------------------------------------------------------------- 1 | package com.r3.corda.lib.tokens.workflows.internal.flows.finality 2 | 3 | import co.paralleluniverse.fibers.Suspendable 4 | import net.corda.core.crypto.SecureHash 5 | import net.corda.core.flows.FlowException 6 | import net.corda.core.flows.FlowLogic 7 | import net.corda.core.flows.FlowSession 8 | import net.corda.core.flows.ReceiveTransactionFlow 9 | import net.corda.core.identity.Party 10 | import net.corda.core.node.StatesToRecord 11 | import net.corda.core.transactions.SignedTransaction 12 | import net.corda.core.utilities.unwrap 13 | 14 | class ObserverAwareFinalityFlowHandler @JvmOverloads constructor(val otherSession: FlowSession, val expectedTxId: SecureHash? = null) : FlowLogic() { 15 | @Suspendable 16 | override fun call(): SignedTransaction? { 17 | val role = otherSession.receive().unwrap { it } 18 | val statesToRecord = role.toStatesToRecord() 19 | 20 | return if (serviceHub.myInfo.isLegalIdentity(otherSession.counterparty)) null else { 21 | subFlow(object : ReceiveTransactionFlow(otherSession, true, statesToRecord) { 22 | override fun checkBeforeRecording(stx: SignedTransaction) { 23 | val ledgerTransaction = stx.toLedgerTransaction(serviceHub, false) 24 | val wellKnownParticipantsAndIssuers: Set = ledgerTransaction.getParticipantsAndIssuersExcludingUnknown(serviceHub) 25 | val weAreNotAParticipant = ourIdentity !in wellKnownParticipantsAndIssuers 26 | 27 | if (weAreNotAParticipant && role == TransactionRole.PARTICIPANT) { 28 | throw FlowException("Our identity is not a transaction participant, but we were sent the PARTICIPANT role.") 29 | } 30 | 31 | require(expectedTxId == null || expectedTxId == stx.id) { 32 | "We expected to receive transaction with ID $expectedTxId but instead got ${stx.id}. Transaction was" + 33 | "not recorded and nor its states sent to the vault." 34 | } 35 | } 36 | }) 37 | } 38 | } 39 | 40 | @Suspendable 41 | fun TransactionRole.toStatesToRecord(): StatesToRecord = when (this) { 42 | TransactionRole.PARTICIPANT -> StatesToRecord.ONLY_RELEVANT 43 | TransactionRole.OBSERVER -> StatesToRecord.ALL_VISIBLE 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/internal/schemas/DistributionRecord.kt: -------------------------------------------------------------------------------- 1 | package com.r3.corda.lib.tokens.workflows.internal.schemas 2 | 3 | import net.corda.core.identity.Party 4 | import net.corda.core.schemas.MappedSchema 5 | import net.corda.core.serialization.CordaSerializable 6 | import org.hibernate.annotations.Type 7 | import java.util.* 8 | import javax.persistence.* 9 | 10 | object DistributionRecordSchema 11 | 12 | object DistributionRecordSchemaV1 : MappedSchema( 13 | schemaFamily = DistributionRecordSchema.javaClass, 14 | version = 1, 15 | mappedTypes = listOf(DistributionRecord::class.java) 16 | ) 17 | 18 | @CordaSerializable 19 | @Entity 20 | @Table(name = "distribution_record", indexes = [Index(name = "dist_record_idx", columnList = "linear_id")]) 21 | class DistributionRecord( 22 | 23 | @Id 24 | @GeneratedValue 25 | var id: Long, 26 | 27 | @Column(name = "linear_id", nullable = false) 28 | @Type(type = "uuid-char") 29 | var linearId: UUID, 30 | 31 | @Column(name = "party", nullable = false) 32 | var party: Party 33 | 34 | ) { 35 | constructor(linearId: UUID, party: Party) : this(0, linearId, party) 36 | } -------------------------------------------------------------------------------- /workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/internal/selection/NonFungibleTokenSelection.kt: -------------------------------------------------------------------------------- 1 | package com.r3.corda.lib.tokens.workflows.internal.selection 2 | 3 | import co.paralleluniverse.fibers.Suspendable 4 | import com.r3.corda.lib.tokens.contracts.commands.MoveTokenCommand 5 | import com.r3.corda.lib.tokens.contracts.commands.RedeemTokenCommand 6 | import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken 7 | import com.r3.corda.lib.tokens.contracts.utilities.withNotary 8 | import com.r3.corda.lib.tokens.workflows.types.PartyAndToken 9 | import com.r3.corda.lib.tokens.workflows.utilities.addNotaryWithCheck 10 | import com.r3.corda.lib.tokens.workflows.utilities.addTokenTypeJar 11 | import com.r3.corda.lib.tokens.workflows.utilities.heldTokenCriteria 12 | import net.corda.core.contracts.StateAndRef 13 | import net.corda.core.node.services.VaultService 14 | import net.corda.core.node.services.queryBy 15 | import net.corda.core.node.services.vault.QueryCriteria 16 | import net.corda.core.transactions.TransactionBuilder 17 | 18 | @Suspendable 19 | fun generateMoveNonFungible( 20 | partyAndToken: PartyAndToken, 21 | vaultService: VaultService, 22 | queryCriteria: QueryCriteria? 23 | ): Pair, NonFungibleToken> { 24 | val query = queryCriteria ?: heldTokenCriteria(partyAndToken.token) 25 | val criteria = heldTokenCriteria(partyAndToken.token).and(query) 26 | val nonFungibleTokens = vaultService.queryBy(criteria).states 27 | // There can be multiple non-fungible tokens of the same TokenType. E.g. There can be multiple House tokens, each 28 | // with a different address. Whilst they have the same TokenType, they are still non-fungible. Therefore care must 29 | // be taken to ensure that only one token is returned for each query. As non-fungible tokens are also LinearStates, 30 | // the linearID can be used to ensure you only get one result. 31 | require(nonFungibleTokens.size == 1) { "Your query wasn't specific enough and returned multiple non-fungible tokens." } 32 | val input = nonFungibleTokens.single() 33 | val nonFungibleState = input.state.data 34 | val output = nonFungibleState.withNewHolder(partyAndToken.party) 35 | return Pair(input, output) 36 | } 37 | 38 | @Suspendable 39 | fun generateMoveNonFungible( 40 | transactionBuilder: TransactionBuilder, 41 | partyAndToken: PartyAndToken, 42 | vaultService: VaultService, 43 | queryCriteria: QueryCriteria? 44 | ): TransactionBuilder { 45 | val (input, output) = generateMoveNonFungible(partyAndToken, vaultService, queryCriteria) 46 | val notary = input.state.notary 47 | addTokenTypeJar(listOf(input.state.data, output), transactionBuilder) 48 | addNotaryWithCheck(transactionBuilder, notary) 49 | val signingKey = input.state.data.holder.owningKey 50 | 51 | return transactionBuilder.apply { 52 | val currentInputSize = inputStates().size 53 | val currentOutputSize = outputStates().size 54 | addInputState(input) 55 | addOutputState(state = output withNotary notary) 56 | addCommand(MoveTokenCommand(output.token, inputs = listOf(currentInputSize), outputs = listOf(currentOutputSize)), signingKey) 57 | } 58 | } 59 | 60 | 61 | // All check should be performed before. 62 | @Suspendable 63 | fun generateExitNonFungible(txBuilder: TransactionBuilder, moveStateAndRef: StateAndRef) { 64 | val nonFungibleToken = moveStateAndRef.state.data // TODO What if redeeming many non-fungible assets. 65 | addTokenTypeJar(nonFungibleToken, txBuilder) 66 | val issuerKey = nonFungibleToken.token.issuer.owningKey 67 | val moveKey = nonFungibleToken.holder.owningKey 68 | txBuilder.apply { 69 | val currentInputSize = inputStates().size 70 | addInputState(moveStateAndRef) 71 | addCommand(RedeemTokenCommand(nonFungibleToken.token, listOf(currentInputSize)), issuerKey, moveKey) 72 | } 73 | } -------------------------------------------------------------------------------- /workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/types/PartyAndAmount.kt: -------------------------------------------------------------------------------- 1 | package com.r3.corda.lib.tokens.workflows.types 2 | 3 | import com.r3.corda.lib.tokens.contracts.types.TokenType 4 | import net.corda.core.contracts.Amount 5 | import net.corda.core.identity.AbstractParty 6 | import net.corda.core.serialization.CordaSerializable 7 | 8 | /** 9 | * A simple holder for a (possibly anonymous) [AbstractParty] and a quantity of tokens. 10 | * Used in [generateMove] to define what [amount] of token [T] [party] should receive. 11 | */ 12 | @CordaSerializable 13 | data class PartyAndAmount(val party: AbstractParty, val amount: Amount) 14 | 15 | /** 16 | * A simple holder for a (possibly anonymous) [AbstractParty] and a token. 17 | * Used in [generateMove] to define what token [T] [party] should receive. 18 | */ 19 | @CordaSerializable 20 | data class PartyAndToken(val party: AbstractParty, val token: TokenType) 21 | 22 | fun Iterable>.toPairs() = map { Pair(it.party, it.amount) } 23 | 24 | -------------------------------------------------------------------------------- /workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/utilities/NonFungibleTokenBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.r3.corda.lib.tokens.workflows.utilities 2 | 3 | import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken 4 | import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType 5 | import com.r3.corda.lib.tokens.contracts.types.TokenType 6 | import com.r3.corda.lib.tokens.contracts.utilities.heldBy 7 | import com.r3.corda.lib.tokens.contracts.utilities.issuedBy 8 | import com.r3.corda.lib.tokens.workflows.TokenBuilderException 9 | import net.corda.core.contracts.Amount 10 | import net.corda.core.identity.Party 11 | 12 | /** 13 | * A utility class designed for Java developers to more easily access Kotlin DSL 14 | * functions to build non-fungible tokens and their component classes. 15 | * 16 | * This function vaguely follows a builder pattern, design choices were made 17 | * to emulate Kotlin syntax as closely as possible for an easily transferable 18 | * developer experience in Java. 19 | */ 20 | class NonFungibleTokenBuilder { 21 | private lateinit var tokenType: TokenType 22 | private lateinit var issuer: Party 23 | private lateinit var holder: Party 24 | 25 | /** 26 | * Replicates the Kotlin DSL [ofTokenType] infix function. Supplies a [TokenType] to the builder 27 | * which will be used to build an [IssuedTokenType]. 28 | * 29 | * @param t The token type that will be used to build an [IssuedTokenType] 30 | */ 31 | fun ofTokenType(t: T): NonFungibleTokenBuilder = this.apply { this.tokenType = t } 32 | 33 | /** 34 | * Replicates the Kotlin DSL [issuedBy] infix function. Supplies a [Party] to the builder 35 | * representing the identity of the issuer of a non-fungible [IssuedTokenType]. 36 | * 37 | * @param party The issuing identity that will be used to build an [IssuedTokenType] 38 | */ 39 | fun issuedBy(party: Party): NonFungibleTokenBuilder = this.apply { this.issuer = party } 40 | 41 | /** 42 | * Replicates the Kotlin DSL [heldBy] infix function. Supplies a [Party] to the builder 43 | * representing the identity of the holder of a new non-fungible token. 44 | * 45 | * @param party The identity of the holder that will be used to build an [Amount] of an [IssuedTokenType]. 46 | */ 47 | fun heldBy(party: Party): NonFungibleTokenBuilder = this.apply { this.holder = party } 48 | 49 | /** 50 | * Builds an [IssuedTokenType]. This function will throw a [TokenBuilderException] if the appropriate 51 | * builder methods have not been called: [ofTokenType], [issuedBy]. 52 | */ 53 | @Throws(TokenBuilderException::class) 54 | fun buildIssuedTokenType(): IssuedTokenType = when { 55 | !::tokenType.isInitialized -> { throw TokenBuilderException("A token type has not been provided to the builder.") } 56 | !::issuer.isInitialized -> { throw TokenBuilderException("A token issuer has not been provided to the builder.") } 57 | else -> { tokenType issuedBy issuer } 58 | } 59 | 60 | /** 61 | * Builds a [NonFungibleToken] state. This function will throw a [TokenBuilderException] if the appropriate 62 | * builder methods have not been called: [ofTokenType], [issuedBy], [heldBy]. 63 | */ 64 | @Throws(TokenBuilderException::class) 65 | fun buildNonFungibleToken(): NonFungibleToken = when { 66 | ::holder.isInitialized -> { buildIssuedTokenType() heldBy holder } 67 | else -> { throw TokenBuilderException("A token holder has not been provided to the builder.") } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /workflows/src/main/kotlin/com/r3/corda/lib/tokens/workflows/utilities/TokenUtilities.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("TokenUtilities") 2 | package com.r3.corda.lib.tokens.workflows.utilities 3 | 4 | import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken 5 | import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType 6 | import net.corda.core.contracts.UniqueIdentifier 7 | import net.corda.core.identity.AbstractParty 8 | 9 | /** 10 | * Creates a [NonFungibleToken] from an [IssuedTokenType]. 11 | * E.g. IssuedTokenType -> NonFungibleToken. 12 | * This function must exist outside of the contracts module as creating a unique identifier is non-deterministic. 13 | */ 14 | infix fun IssuedTokenType.heldBy(owner: AbstractParty): NonFungibleToken = _heldBy(owner) 15 | 16 | private infix fun IssuedTokenType._heldBy(owner: AbstractParty): NonFungibleToken { 17 | return NonFungibleToken(this, owner, UniqueIdentifier()) 18 | } 19 | -------------------------------------------------------------------------------- /workflows/src/main/resources/migration/distribution-record-schema-v1.changelog-master.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /workflows/src/main/resources/migration/distribution-record-schema.changelog-init.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /workflows/src/main/resources/migration/fungible-token-schema-update-1.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /workflows/src/main/resources/migration/fungible-token-schema-v1.changelog-master.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /workflows/src/main/resources/migration/fungible-token-schema.changelog-1.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /workflows/src/main/resources/migration/fungible-token-schema.changelog-init.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /workflows/src/main/resources/migration/non-fungible-token-schema-update-1.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /workflows/src/main/resources/migration/non-fungible-token-schema-v1.changelog-master.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /workflows/src/main/resources/migration/non-fungible-token-schema.changelog-init.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /workflows/src/main/resources/migration/token-schema-v1.changelog-master.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /workflows/src/test/java/com/r3/corda/lib/tokens/money/CurrencyAccessFromJavaTest.java: -------------------------------------------------------------------------------- 1 | package com.r3.corda.lib.tokens.money; 2 | 3 | import com.r3.corda.lib.tokens.contracts.types.TokenType; 4 | import org.junit.Test; 5 | 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | public class CurrencyAccessFromJavaTest { 10 | 11 | /** 12 | * Sanity check for easy access of digital currency TokenTypes from java code 13 | */ 14 | @Test 15 | public void javaWrappedDigitalCurrencyIsIdenticalToKotlinCompanionObject() throws Exception { 16 | 17 | List digitalCurrencies = Arrays.asList("XRP", "BTC", "ETH", "DOGE"); 18 | 19 | digitalCurrencies.forEach((currencyCode) -> { 20 | TokenType kotlinDigital = DigitalCurrency.Companion.getInstance(currencyCode); 21 | TokenType javaStatic = DigitalCurrency.getInstance(currencyCode); 22 | 23 | assert (kotlinDigital.getTokenIdentifier().equals(javaStatic.getTokenIdentifier())); 24 | assert (kotlinDigital.getFractionDigits() == javaStatic.getFractionDigits()); 25 | }); 26 | } 27 | 28 | /** 29 | * Sanity check for easy access of fiat currency TokenTypes from java code 30 | */ 31 | @Test 32 | public void javaWrappedFiatCurrencyIsIdenticalToKotlinCompanionObject() throws Exception { 33 | 34 | List fiatCurrencies = Arrays.asList("GBP", "USD", "EUR", "CHF", "JPY", "CAD", "AUD", "NZD"); 35 | 36 | fiatCurrencies.forEach((currencyCode) -> { 37 | TokenType kotlinFiat = FiatCurrency.Companion.getInstance(currencyCode); 38 | TokenType javaStatic = FiatCurrency.getInstance(currencyCode); 39 | 40 | assert (kotlinFiat.getTokenIdentifier().equals(javaStatic.getTokenIdentifier())); 41 | assert (kotlinFiat.getFractionDigits() == javaStatic.getFractionDigits()); 42 | }); 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /workflows/src/test/java/com/r3/corda/lib/tokens/workflows/utilities/NotaryUtilitiesFromJavaTest.java: -------------------------------------------------------------------------------- 1 | package com.r3.corda.lib.tokens.workflows.utilities; 2 | 3 | import com.r3.corda.lib.tokens.workflows.JITMockNetworkTests; 4 | import net.corda.core.identity.CordaX500Name; 5 | import net.corda.core.identity.Party; 6 | import net.corda.testing.node.MockNetwork; 7 | import net.corda.testing.node.StartedMockNode; 8 | import org.junit.Before; 9 | import org.junit.Rule; 10 | import org.junit.Test; 11 | import org.junit.rules.ExpectedException; 12 | 13 | public class NotaryUtilitiesFromJavaTest extends JITMockNetworkTests { 14 | 15 | private MockNetwork mockNetwork; 16 | private StartedMockNode a; 17 | 18 | @Before 19 | public void setup() { 20 | a = node(new CordaX500Name("Alice", "NYC", "US")); 21 | } 22 | 23 | @Rule 24 | public final ExpectedException exception = ExpectedException.none(); 25 | 26 | /** 27 | * Sanity check for notary utilities wrapped in class for access from Java. 28 | */ 29 | @Test 30 | public void javaWrappedNotarySelectionIsIdenticalToKotlinCompanionObject() throws Exception { 31 | Party javaWrappedAllArgs = NotaryUtilities.getPreferredNotary(a.getServices(), NotaryUtilities.firstNotary()); 32 | Party javaWrappedDefaultArgs = NotaryUtilities.getPreferredNotary(a.getServices()); 33 | assert(javaWrappedAllArgs.equals(javaWrappedDefaultArgs)); 34 | 35 | Party javaWrappedFirstNotary = NotaryUtilities.getPreferredNotary(a.getServices()); 36 | assert(javaWrappedAllArgs.equals(javaWrappedFirstNotary)); 37 | 38 | Party kotlinRandomNotary = NotaryUtilities.randomNotary().invoke(a.getServices()); 39 | assert(javaWrappedAllArgs.equals(kotlinRandomNotary)); 40 | } 41 | 42 | /** 43 | * Sanity check for preferred notary backup selector. 44 | */ 45 | @Test 46 | public void javaWrappedNotarySelectionWorksWithBackupSelection() throws Exception { 47 | Party preferredNotaryWithBackup = NotaryUtilities.getPreferredNotary(a.getServices(), NotaryUtilities.firstNotary()); 48 | Party preferredNotary = NotaryUtilities.getPreferredNotary(a.getServices()); 49 | assert(preferredNotary.equals(preferredNotaryWithBackup)); 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /workflows/src/test/kotlin/com/r3/corda/lib/tokens/workflows/LedgerTestWithPersistence.kt: -------------------------------------------------------------------------------- 1 | package com.r3.corda.lib.tokens.workflows 2 | 3 | import com.nhaarman.mockito_kotlin.whenever 4 | import net.corda.core.identity.CordaX500Name 5 | import net.corda.core.node.services.IdentityService 6 | import net.corda.coretesting.internal.rigorousMock 7 | import net.corda.testing.common.internal.testNetworkParameters 8 | import net.corda.testing.core.DUMMY_NOTARY_NAME 9 | import net.corda.testing.core.SerializationEnvironmentRule 10 | import net.corda.testing.core.TestIdentity 11 | import net.corda.testing.node.MockServices 12 | import org.junit.Rule 13 | import org.mockito.Mockito 14 | 15 | abstract class LedgerTestWithPersistence { 16 | 17 | private val mockIdentityService = rigorousMock().also { 18 | Mockito.doReturn(ALICE.party).whenever(it).partyFromKey(ALICE.publicKey) 19 | Mockito.doReturn(BOB.party).whenever(it).partyFromKey(BOB.publicKey) 20 | Mockito.doReturn(ISSUER.party).whenever(it).partyFromKey(ISSUER.publicKey) 21 | Mockito.doReturn(NOTARY.party).whenever(it).partyFromKey(NOTARY.publicKey) 22 | Mockito.doReturn(NOTARY.party).whenever(it).wellKnownPartyFromAnonymous(NOTARY.party) 23 | Mockito.doReturn(ALICE.party).whenever(it).wellKnownPartyFromAnonymous(ALICE.party) 24 | Mockito.doReturn(ISSUER.party).whenever(it).wellKnownPartyFromAnonymous(ISSUER.party) 25 | Mockito.doReturn(ISSUER.party).whenever(it).wellKnownPartyFromX500Name(ISSUER.party.name) 26 | Mockito.doReturn(NOTARY.party).whenever(it).wellKnownPartyFromX500Name(NOTARY.party.name) 27 | Mockito.doReturn(ALICE.party).whenever(it).wellKnownPartyFromX500Name(ALICE.party.name) 28 | 29 | } 30 | 31 | protected companion object { 32 | val NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20) 33 | val ISSUER = TestIdentity(CordaX500Name("ISSUER", "London", "GB")) 34 | val ALICE = TestIdentity(CordaX500Name("ALICE", "London", "GB")) 35 | val BOB = TestIdentity(CordaX500Name("BOB", "London", "GB")) 36 | } 37 | 38 | @Rule 39 | @JvmField 40 | val testSerialization = SerializationEnvironmentRule() 41 | 42 | private val aliceDbAndServices = MockServices.makeTestDatabaseAndMockServices( 43 | cordappPackages = listOf("com.r3.corda.lib.tokens"), 44 | initialIdentity = ALICE, 45 | identityService = mockIdentityService, 46 | networkParameters = testNetworkParameters(minimumPlatformVersion = 6) 47 | ) 48 | 49 | protected val aliceDB = aliceDbAndServices.first 50 | protected val aliceServices = aliceDbAndServices.second 51 | 52 | private val bobDbAndServices = MockServices.makeTestDatabaseAndMockServices( 53 | cordappPackages = listOf("com.r3.corda.lib.tokens"), 54 | initialIdentity = BOB, 55 | identityService = mockIdentityService, 56 | networkParameters = testNetworkParameters(minimumPlatformVersion = 6) 57 | ) 58 | 59 | protected val bobDB = aliceDbAndServices.first 60 | protected val bobServices = aliceDbAndServices.second 61 | 62 | private val issuerDbAndServices = MockServices.makeTestDatabaseAndMockServices( 63 | cordappPackages = listOf("com.r3.corda.lib.tokens"), 64 | initialIdentity = ISSUER, 65 | identityService = mockIdentityService, 66 | networkParameters = testNetworkParameters(minimumPlatformVersion = 6) 67 | ) 68 | 69 | protected val issuerDB = aliceDbAndServices.first 70 | protected val issuerServices = aliceDbAndServices.second 71 | 72 | } -------------------------------------------------------------------------------- /workflows/src/test/kotlin/com/r3/corda/lib/tokens/workflows/MockNetworkTest.kt: -------------------------------------------------------------------------------- 1 | package com.r3.corda.lib.tokens.workflows 2 | 3 | import net.corda.core.identity.CordaX500Name 4 | import net.corda.testing.common.internal.testNetworkParameters 5 | import net.corda.testing.internal.chooseIdentity 6 | import net.corda.testing.node.MockNetwork 7 | import net.corda.testing.node.MockNetworkParameters 8 | import net.corda.testing.node.StartedMockNode 9 | import net.corda.testing.node.TestCordapp 10 | import org.junit.After 11 | import org.junit.Before 12 | import org.junit.Rule 13 | import org.junit.rules.Timeout 14 | import java.util.concurrent.TimeUnit 15 | 16 | abstract class MockNetworkTest(val names: List) { 17 | 18 | @get:Rule 19 | val timeoutRule = Timeout(5, TimeUnit.MINUTES) 20 | 21 | constructor(vararg names: String) : this(names.map { CordaX500Name(it, "London", "GB") }) 22 | 23 | constructor(numberOfNodes: Int) : this(*(1..numberOfNodes).map { "Party${it.toChar() + 64}" }.toTypedArray()) 24 | 25 | protected val network = MockNetwork(parameters = MockNetworkParameters( 26 | cordappsForAllNodes = listOf(TestCordapp.findCordapp("com.r3.corda.lib.tokens.money"), 27 | TestCordapp.findCordapp("com.r3.corda.lib.tokens.contracts"), 28 | TestCordapp.findCordapp("com.r3.corda.lib.tokens.workflows"), 29 | TestCordapp.findCordapp("com.r3.corda.lib.tokens.selection"), 30 | TestCordapp.findCordapp("com.r3.corda.lib.tokens.testing"), 31 | TestCordapp.findCordapp("com.r3.corda.lib.ci")), 32 | threadPerNode = true, 33 | networkParameters = testNetworkParameters(minimumPlatformVersion = 6) 34 | ) 35 | ) 36 | 37 | /** The nodes which makes up the network. */ 38 | protected lateinit var nodes: List 39 | protected lateinit var nodesByName: Map 40 | 41 | /** Override this to assign each node to a variable for ease of use. */ 42 | @Before 43 | abstract fun initialiseNodes() 44 | 45 | @Before 46 | fun setupNetwork() { 47 | nodes = names.map { network.createPartyNode(it) } 48 | val nodeMap = LinkedHashMap() 49 | nodes.forEachIndexed { index, node -> 50 | nodeMap[index] = node 51 | nodeMap[node.info.chooseIdentity().name.organisation] = node 52 | } 53 | nodesByName = nodeMap 54 | } 55 | 56 | @After 57 | fun tearDownNetwork() { 58 | // Required to get around mysterious KryoException 59 | try{ 60 | network.stopNodes() 61 | } catch (e: Exception) { 62 | println(e.localizedMessage) 63 | } 64 | } 65 | 66 | protected val NOTARY: StartedMockNode get() = network.defaultNotaryNode 67 | } -------------------------------------------------------------------------------- /workflows/src/test/kotlin/com/r3/corda/lib/tokens/workflows/TokenSelectionTestFlows.kt: -------------------------------------------------------------------------------- 1 | package com.r3.corda.lib.tokens.workflows 2 | 3 | import co.paralleluniverse.fibers.Suspendable 4 | import com.r3.corda.lib.tokens.contracts.states.FungibleToken 5 | import com.r3.corda.lib.tokens.contracts.types.TokenType 6 | import com.r3.corda.lib.tokens.selection.TokenQueryBy 7 | import com.r3.corda.lib.tokens.selection.memory.selector.LocalTokenSelector 8 | import com.r3.corda.lib.tokens.selection.memory.services.VaultWatcherService 9 | import net.corda.core.concurrent.CordaFuture 10 | import net.corda.core.contracts.Amount 11 | import net.corda.core.contracts.StateAndRef 12 | import net.corda.core.flows.FlowLogic 13 | import net.corda.core.internal.FlowAsyncOperation 14 | import net.corda.core.internal.executeAsync 15 | import java.security.PublicKey 16 | import java.util.concurrent.CompletableFuture 17 | import java.util.concurrent.Executors 18 | import java.util.concurrent.Future 19 | 20 | 21 | val e = Executors.newSingleThreadExecutor() 22 | 23 | class SuspendingSelector(val owningKey: PublicKey, 24 | val amount: Amount) : FlowLogic>>() { 25 | 26 | 27 | @Suspendable 28 | override fun call(): List> { 29 | val vaultWatcherService = serviceHub.cordaService(VaultWatcherService::class.java) 30 | val localTokenSelector = LocalTokenSelector(serviceHub, vaultWatcherService) 31 | 32 | val selectedTokens = localTokenSelector.selectTokens(requiredAmount = amount, queryBy = TokenQueryBy()) 33 | 34 | println("SUSPENDING:::: ${runId.uuid}") 35 | 36 | val string = executeAsync(object : FlowAsyncOperation { 37 | override fun execute(deduplicationId: String): CordaFuture { 38 | val f = CompletableFuture() 39 | e.submit { 40 | Thread.sleep(1000) 41 | f.complete("yes") 42 | } 43 | return object : Future by f, CordaFuture { 44 | override fun then(callback: (CordaFuture) -> W) { 45 | f.thenAccept { callback(this) } 46 | } 47 | 48 | override fun toCompletableFuture(): CompletableFuture { 49 | return f 50 | } 51 | } 52 | } 53 | }) 54 | 55 | println("RESUMED:::: ${runId.uuid} :: $string") 56 | 57 | return selectedTokens 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /workflows/src/test/kotlin/com/r3/corda/lib/tokens/workflows/TokenSelectionWithInFlowTest.kt: -------------------------------------------------------------------------------- 1 | package com.r3.corda.lib.tokens.workflows 2 | 3 | import com.r3.corda.lib.tokens.contracts.states.FungibleToken 4 | import com.r3.corda.lib.tokens.contracts.utilities.heldBy 5 | import com.r3.corda.lib.tokens.contracts.utilities.issuedBy 6 | import com.r3.corda.lib.tokens.contracts.utilities.of 7 | import com.r3.corda.lib.tokens.money.BTC 8 | import com.r3.corda.lib.tokens.workflows.flows.rpc.IssueTokens 9 | import net.corda.core.contracts.Amount 10 | import net.corda.core.utilities.getOrThrow 11 | import net.corda.testing.core.* 12 | import net.corda.testing.node.internal.InternalMockNetwork 13 | import net.corda.testing.node.internal.InternalMockNodeParameters 14 | import net.corda.testing.node.internal.startFlow 15 | import org.hamcrest.CoreMatchers.* 16 | import org.junit.* 17 | 18 | class TokenSelectionWithInFlowTest { 19 | 20 | @Test 21 | fun `should allow selection of tokens already issued from within a flow`() { 22 | 23 | val mockNet = InternalMockNetwork( 24 | cordappPackages = listOf( 25 | "com.r3.corda.lib.tokens.money", 26 | "com.r3.corda.lib.tokens.selection", 27 | "com.r3.corda.lib.tokens.contracts", 28 | "com.r3.corda.lib.tokens.workflows" 29 | ) 30 | ) 31 | 32 | try { 33 | val aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME)) 34 | val issuerNode = mockNet.createNode(InternalMockNodeParameters(legalName = CHARLIE_NAME)) 35 | val alice = aliceNode.info.singleIdentity() 36 | val issuer = issuerNode.info.singleIdentity() 37 | 38 | val btc = 100000 of BTC issuedBy issuer heldBy alice 39 | val resultFuture = issuerNode.services.startFlow(IssueTokens(listOf(btc))).resultFuture 40 | mockNet.runNetwork() 41 | val issueResultTx = resultFuture.get() 42 | val issuedStateRef = issueResultTx.coreTransaction.outRefsOfType().single() 43 | 44 | val tokensFuture = aliceNode.services.startFlow(SuspendingSelector(alice.owningKey, Amount(1, BTC))).resultFuture 45 | mockNet.runNetwork() 46 | val selectedToken = tokensFuture.getOrThrow().single() 47 | 48 | Assert.assertThat(issuedStateRef, `is`(equalTo(selectedToken))) 49 | } finally { 50 | mockNet.stopNodes() 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /workflows/src/test/kotlin/com/r3/corda/lib/tokens/workflows/TransactionUtilityTests.kt: -------------------------------------------------------------------------------- 1 | package com.r3.corda.lib.tokens.workflows 2 | 3 | import com.r3.corda.lib.tokens.contracts.utilities.* 4 | import com.r3.corda.lib.tokens.money.GBP 5 | import net.corda.core.contracts.StateAndRef 6 | import net.corda.core.contracts.StateRef 7 | import net.corda.core.contracts.TransactionState 8 | import net.corda.core.crypto.SecureHash 9 | import net.corda.testing.core.TestIdentity 10 | import org.junit.Test 11 | 12 | class TransactionUtilityTests { 13 | val ALICE: TestIdentity = TestIdentity.fresh("ALICE") 14 | val ISSUER_ONE: TestIdentity = TestIdentity.fresh("ISSUER ONE") 15 | val ISSUER_TWO: TestIdentity = TestIdentity.fresh("ISSUER TWO") 16 | val NOTARY: TestIdentity = TestIdentity.fresh("NOTARY") 17 | 18 | @Test 19 | fun `sum issued tokens with different issuers`() { 20 | val a = 10.GBP issuedBy ISSUER_ONE.party heldBy ALICE.party 21 | val b = 25.GBP issuedBy ISSUER_ONE.party heldBy ALICE.party 22 | val c = 40.GBP issuedBy ISSUER_TWO.party heldBy ALICE.party 23 | val d = 55.GBP issuedBy ISSUER_TWO.party heldBy ALICE.party 24 | val tokens = listOf(a, b, c, d) 25 | val result = tokens.filterTokensByIssuer(ISSUER_ONE.party).sumTokenStatesOrThrow() 26 | println(result) 27 | val tokenStateAndRefs = tokens.map { 28 | StateAndRef(TransactionState(it, notary = NOTARY.party), StateRef(SecureHash.zeroHash, 0)) 29 | } 30 | val resultTwo = tokenStateAndRefs.filterTokenStateAndRefsByIssuer(ISSUER_TWO.party).sumTokenStateAndRefs() 31 | println(resultTwo) 32 | } 33 | } -------------------------------------------------------------------------------- /workflows/src/test/kotlin/com/r3/corda/lib/tokens/workflows/factories/TestEvolvableTokenTypeFactory.kt: -------------------------------------------------------------------------------- 1 | package com.r3.corda.lib.tokens.workflows.factories 2 | 3 | import com.r3.corda.lib.tokens.testing.states.TestEvolvableTokenType 4 | import com.r3.corda.lib.tokens.workflows.JITMockNetworkTests 5 | import net.corda.core.contracts.UniqueIdentifier 6 | import net.corda.core.identity.Party 7 | 8 | class TestEvolvableTokenTypeFactory(val testSuite: JITMockNetworkTests, val maintainerA: String, val maintainerB: String, val observerC: String, val observerD: String) { 9 | 10 | fun withOneMaintainer(linearId: UniqueIdentifier = UniqueIdentifier(), maintainer: Party = testSuite.identity(maintainerA)): TestEvolvableTokenType { 11 | return TestEvolvableTokenType( 12 | maintainers = listOf(maintainer), 13 | linearId = linearId 14 | ) 15 | } 16 | 17 | fun withTwoMaintainers(linearId: UniqueIdentifier = UniqueIdentifier(), maintainer1: Party = testSuite.identity(maintainerA), maintainer2: Party = testSuite.identity(maintainerB)): TestEvolvableTokenType { 18 | return TestEvolvableTokenType( 19 | maintainers = listOf(maintainer1, maintainer2), 20 | linearId = linearId 21 | ) 22 | } 23 | 24 | fun withOneMaintainerAndOneParticipant(linearId: UniqueIdentifier = UniqueIdentifier(), maintainer: Party = testSuite.identity(maintainerA), observer: Party = testSuite.identity(observerC)): TestEvolvableTokenType { 25 | return TestEvolvableTokenType( 26 | maintainers = listOf(maintainer), 27 | observers = listOf(observer), 28 | linearId = linearId 29 | ) 30 | } 31 | 32 | fun withOneMaintainerAndTwoParticipants(linearId: UniqueIdentifier = UniqueIdentifier(), maintainer1: Party = testSuite.identity(maintainerA), observer1: Party = testSuite.identity(observerC), observer2: Party = testSuite.identity(observerD)): TestEvolvableTokenType { 33 | return TestEvolvableTokenType( 34 | maintainers = listOf(maintainer1), 35 | observers = listOf(observer1, observer2), 36 | linearId = linearId 37 | ) 38 | } 39 | 40 | fun withTwoMaintainersAndOneParticipant(linearId: UniqueIdentifier = UniqueIdentifier(), maintainer1: Party = testSuite.identity(maintainerA), maintainer2: Party = testSuite.identity(maintainerB), observer1: Party = testSuite.identity(observerC)): TestEvolvableTokenType { 41 | return TestEvolvableTokenType( 42 | maintainers = listOf(maintainer1, maintainer2), 43 | observers = listOf(observer1), 44 | linearId = linearId 45 | ) 46 | } 47 | 48 | fun withTwoMaintainersAndTwoParticipants(linearId: UniqueIdentifier = UniqueIdentifier(), maintainer1: Party = testSuite.identity(maintainerA), maintainer2: Party = testSuite.identity(maintainerB), observer1: Party = testSuite.identity(observerC), observer2: Party = testSuite.identity(observerD)): TestEvolvableTokenType { 49 | return TestEvolvableTokenType( 50 | maintainers = listOf(maintainer1, maintainer2), 51 | observers = listOf(observer1, observer2), 52 | linearId = linearId 53 | ) 54 | } 55 | 56 | fun withDifferingMaintainersAndParticipants(linearId: UniqueIdentifier = UniqueIdentifier(), maintainer: Party = testSuite.identity(maintainerA), participant: Party = testSuite.identity(observerC)): TestEvolvableTokenType { 57 | return TestEvolvableTokenType( 58 | maintainers = listOf(maintainer), 59 | participants = listOf(participant), 60 | linearId = linearId 61 | ) 62 | } 63 | } --------------------------------------------------------------------------------