mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 15:35:09 +00:00
Java chaincode for asset transfer events sample. Tested/Compatible with js application
Signed-off-by: Sijo Cherian <sijo@ibm.com>
This commit is contained in:
parent
8c9fd980b9
commit
757b0419b7
11 changed files with 1071 additions and 0 deletions
91
asset-transfer-events/README.md
Normal file
91
asset-transfer-events/README.md
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# Asset Transfer Events Sample
|
||||
|
||||
The asset transfer events sample demonstrates chaincode events send/receive
|
||||
and the receive of block events. The chaincode events are set by your
|
||||
chaincode which adds the event data to the transaction and are sent when the
|
||||
transaction is committed to the ledger. The block events are published when
|
||||
a block is committed to the ledger, containing all the transaction details
|
||||
within that block.
|
||||
|
||||
For more information about event services on per-channel basis, visit the
|
||||
[Channel-based event service](https://hyperledger-fabric.readthedocs.io/en/latest/peer_event_services.html)
|
||||
page in the Fabric documentation.
|
||||
|
||||
|
||||
## About the Sample
|
||||
|
||||
This sample includes chaincodes and application code in multiple languages.
|
||||
In a use-case similar to basic asset transfer ( see `../asset-transfer-basic` folder)
|
||||
this sample shows sending and receiving of events during create/update/delete of an asset
|
||||
and during transfer of an asset to a new owner.
|
||||
|
||||
### Application
|
||||
The application demonstrates this, using two types of listeners in subsequent sections of `main` function:
|
||||
1. Contract Listener: listen for events in a specific Contract
|
||||
- How to register a contract listener in an application, for chaincode events
|
||||
- How to get the chaincode event name and value from the chaincode event
|
||||
- How to retrieve the transaction and block information from the chaincode event
|
||||
|
||||
2. Block Listener: listen for block level events and parse private-data events
|
||||
- How to register a block listener for full block events
|
||||
- How to retrieve the transaction and block information from the block event
|
||||
- How to register to receive private data associated with transactions, when registering a block listener
|
||||
- How to retrieve the private data collection details from the full block event
|
||||
- This section also shows how to connect to a Gateway with listener that will not listen for commit events. This may be useful when the application does not want to wait for the peer to commit blocks and notify the application.
|
||||
|
||||
|
||||
Follow the comments in `application-javascript/app.js` file, and corresponding output on running this application.
|
||||
Pay attention to the sequence of
|
||||
- smart contract calls (console output like `--> Submit Transaction or --> Evaluate`)
|
||||
- the events received at application end (console output like `<-- Contract Event Received: or <-- Block Event Received`)
|
||||
|
||||
The listener will be notified of an event asynchronously. Notice that events will
|
||||
be posted by the listener after the application code sends the transaction (or after the
|
||||
change is committed to the ledger), but during other application activity unrelated to the event.
|
||||
|
||||
### Smart Contract
|
||||
The smart contract implements (in folder `chaincode-xyz`) following functions to support the application:
|
||||
- CreateAsset
|
||||
- ReadAsset
|
||||
- UpdateAsset
|
||||
- DeleteAsset
|
||||
- TransferAsset
|
||||
|
||||
Note that the asset transfer implemented by the smart contract is a simplified scenario, without ownership validation, meant only to
|
||||
demonstrate the use of sending and receiving events.
|
||||
|
||||
|
||||
## Running the sample
|
||||
|
||||
Like other samples, we will use the Fabric test network to deploy and run ths sample. Follow these step in order.
|
||||
- Create the test network and a channel
|
||||
```
|
||||
cd test-network
|
||||
./network.sh up createChannel -c mychannel -ca
|
||||
```
|
||||
|
||||
- Deploy the chaincode (smart contract)
|
||||
```
|
||||
# to deploy javascript version
|
||||
./network.sh deployCC -ccs 1 -ccv 1 -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -ccl javascript -ccp ./../asset-transfer-events/chaincode-javascript/ -ccn asset-transfer-events-javascript
|
||||
|
||||
# or to deploy java version
|
||||
./network.sh deployCC -ccs 1 -ccv 1 -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -ccl java -ccp ./../asset-transfer-events/chaincode-java/ -ccn asset-transfer-events-java
|
||||
```
|
||||
|
||||
- Run the application
|
||||
```
|
||||
cd application-javascript
|
||||
npm install
|
||||
# ensure this line in app.js have correct chaincode deploy name
|
||||
# const chaincodeName = '...';
|
||||
node app.js
|
||||
```
|
||||
|
||||
|
||||
## Clean up
|
||||
When you are finished, you can bring down the test network. The command will remove all the nodes of the test network, and delete any ledger data that you created:
|
||||
|
||||
```
|
||||
./network.sh down
|
||||
```
|
||||
51
asset-transfer-events/chaincode-java/build.gradle
Normal file
51
asset-transfer-events/chaincode-java/build.gradle
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'application'
|
||||
id 'checkstyle'
|
||||
id 'jacoco'
|
||||
}
|
||||
|
||||
group 'org.hyperledger.fabric.samples'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
dependencies {
|
||||
compileOnly 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+'
|
||||
testImplementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+'
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "https://hyperledger.jfrog.io/hyperledger/fabric-maven"
|
||||
}
|
||||
jcenter()
|
||||
maven {
|
||||
url 'https://jitpack.io'
|
||||
}
|
||||
}
|
||||
|
||||
application {
|
||||
mainClassName = 'org.hyperledger.fabric.contract.ContractRouter'
|
||||
}
|
||||
|
||||
|
||||
checkstyle {
|
||||
toolVersion '8.21'
|
||||
configFile file("config/checkstyle/checkstyle.xml")
|
||||
}
|
||||
|
||||
checkstyleMain {
|
||||
source ='src/main/java'
|
||||
}
|
||||
|
||||
checkstyleTest {
|
||||
source ='src/test/java'
|
||||
}
|
||||
|
||||
jacocoTestReport {
|
||||
dependsOn test
|
||||
}
|
||||
|
||||
installDist.dependsOn check
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
|
||||
<!--
|
||||
|
||||
Checkstyle configuration that matches the Eclipse formatter
|
||||
|
||||
Checkstyle is very configurable. Be sure to read the documentation at
|
||||
http://checkstyle.sourceforge.net (or in your downloaded distribution).
|
||||
|
||||
Most Checks are configurable, be sure to consult the documentation.
|
||||
|
||||
To completely disable a check, just comment it out or delete it from the file.
|
||||
|
||||
Finally, it is worth reading the documentation.
|
||||
|
||||
-->
|
||||
|
||||
<module name="Checker">
|
||||
<!--
|
||||
If you set the basedir property below, then all reported file
|
||||
names will be relative to the specified directory. See
|
||||
https://checkstyle.org/5.x/config.html#Checker
|
||||
|
||||
<property name="basedir" value="${basedir}"/>
|
||||
-->
|
||||
|
||||
<property name="fileExtensions" value="java, properties, xml"/>
|
||||
|
||||
<module name="SuppressionFilter">
|
||||
<property name="file" value="${config_loc}/suppressions.xml"/>
|
||||
<property name="optional" value="false"/>
|
||||
</module>
|
||||
|
||||
<!-- Excludes all 'module-info.java' files -->
|
||||
<!-- See https://checkstyle.org/config_filefilters.html -->
|
||||
<module name="BeforeExecutionExclusionFileFilter">
|
||||
<property name="fileNamePattern" value="module\-info\.java$"/>
|
||||
</module>
|
||||
|
||||
<!-- Checks that a package-info.java file exists for each package. -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_javadoc.html#JavadocPackage -->
|
||||
<!-- <module name="JavadocPackage"/> -->
|
||||
|
||||
<!-- Checks whether files end with a new line. -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_misc.html#NewlineAtEndOfFile -->
|
||||
<module name="NewlineAtEndOfFile"/>
|
||||
|
||||
<!-- Checks that property files contain the same keys. -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_misc.html#Translation -->
|
||||
<module name="Translation"/>
|
||||
|
||||
<!-- Checks for Size Violations. -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_sizes.html -->
|
||||
<module name="FileLength"/>
|
||||
|
||||
<!-- Checks for whitespace -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_whitespace.html -->
|
||||
<module name="FileTabCharacter"/>
|
||||
|
||||
<!-- Miscellaneous other checks. -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_misc.html -->
|
||||
<module name="RegexpSingleline">
|
||||
<property name="format" value="\s+$"/>
|
||||
<property name="minimum" value="0"/>
|
||||
<property name="maximum" value="0"/>
|
||||
<property name="message" value="Line has trailing spaces."/>
|
||||
</module>
|
||||
|
||||
<!-- Checks for Headers -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_header.html -->
|
||||
<!-- <module name="Header"> -->
|
||||
<!-- <property name="headerFile" value="${checkstyle.header.file}"/> -->
|
||||
<!-- <property name="fileExtensions" value="java"/> -->
|
||||
<!-- </module> -->
|
||||
|
||||
<module name="TreeWalker">
|
||||
|
||||
<!-- Checks for Javadoc comments. -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_javadoc.html -->
|
||||
<!-- <module name="JavadocMethod"/> -->
|
||||
<!-- <module name="JavadocType"/> -->
|
||||
<!-- <module name="JavadocVariable"/> -->
|
||||
<!-- <module name="JavadocStyle"/> -->
|
||||
<!-- <module name="MissingJavadocMethod"/> -->
|
||||
|
||||
<!-- Checks for Naming Conventions. -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_naming.html -->
|
||||
<module name="ConstantName"/>
|
||||
<module name="LocalFinalVariableName"/>
|
||||
<module name="LocalVariableName"/>
|
||||
<module name="PackageName"/>
|
||||
<module name="StaticVariableName"/>
|
||||
<module name="TypeName"/>
|
||||
|
||||
<!-- Checks for imports -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_import.html -->
|
||||
<module name="AvoidStarImport"/>
|
||||
<module name="IllegalImport"/> <!-- defaults to sun.* packages -->
|
||||
<module name="RedundantImport"/>
|
||||
<module name="UnusedImports">
|
||||
<property name="processJavadoc" value="false"/>
|
||||
</module>
|
||||
|
||||
<!-- Checks for Size Violations. -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_sizes.html -->
|
||||
<module name="MethodLength"/>
|
||||
<module name="ParameterNumber"/>
|
||||
|
||||
<!-- Checks for whitespace -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_whitespace.html -->
|
||||
<module name="EmptyForIteratorPad"/>
|
||||
<module name="GenericWhitespace"/>
|
||||
<module name="MethodParamPad"/>
|
||||
<module name="NoWhitespaceAfter"/>
|
||||
<module name="NoWhitespaceBefore"/>
|
||||
<module name="OperatorWrap"/>
|
||||
<module name="ParenPad"/>
|
||||
<module name="TypecastParenPad"/>
|
||||
<module name="WhitespaceAfter"/>
|
||||
<module name="WhitespaceAround"/>
|
||||
|
||||
<!-- Modifier Checks -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_modifiers.html -->
|
||||
<module name="ModifierOrder"/>
|
||||
<module name="RedundantModifier"/>
|
||||
|
||||
<!-- Checks for blocks. You know, those {}'s -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_blocks.html -->
|
||||
<module name="AvoidNestedBlocks"/>
|
||||
<module name="EmptyBlock"/>
|
||||
<module name="LeftCurly"/>
|
||||
<module name="NeedBraces"/>
|
||||
<module name="RightCurly"/>
|
||||
|
||||
<!-- Checks for common coding problems -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_coding.html -->
|
||||
<module name="EmptyStatement"/>
|
||||
<module name="EqualsHashCode"/>
|
||||
<module name="HiddenField">
|
||||
<property name="ignoreConstructorParameter" value="true"/>
|
||||
</module>
|
||||
<module name="IllegalInstantiation"/>
|
||||
<module name="InnerAssignment"/>
|
||||
<module name="MissingSwitchDefault"/>
|
||||
<module name="MultipleVariableDeclarations"/>
|
||||
<module name="SimplifyBooleanExpression"/>
|
||||
<module name="SimplifyBooleanReturn"/>
|
||||
|
||||
<!-- Checks for class design -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_design.html -->
|
||||
<module name="DesignForExtension"/>
|
||||
<module name="FinalClass"/>
|
||||
<module name="HideUtilityClassConstructor"/>
|
||||
<module name="InterfaceIsType"/>
|
||||
<module name="VisibilityModifier">
|
||||
<property name="allowPublicFinalFields" value="true"/>
|
||||
</module>
|
||||
|
||||
<!-- Miscellaneous other checks. -->
|
||||
<!-- See http://checkstyle.sourceforge.net/config_misc.html -->
|
||||
<module name="ArrayTypeStyle"/>
|
||||
<module name="FinalParameters"/>
|
||||
<module name="TodoComment"/>
|
||||
<module name="UpperEll"/>
|
||||
|
||||
</module>
|
||||
|
||||
</module>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<!DOCTYPE suppressions PUBLIC
|
||||
"-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
|
||||
"https://checkstyle.org/dtds/suppressions_1_2.dtd">
|
||||
|
||||
<suppressions>
|
||||
</suppressions>
|
||||
BIN
asset-transfer-events/chaincode-java/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
asset-transfer-events/chaincode-java/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
asset-transfer-events/chaincode-java/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
asset-transfer-events/chaincode-java/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
183
asset-transfer-events/chaincode-java/gradlew
vendored
Executable file
183
asset-transfer-events/chaincode-java/gradlew
vendored
Executable file
|
|
@ -0,0 +1,183 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
100
asset-transfer-events/chaincode-java/gradlew.bat
vendored
Normal file
100
asset-transfer-events/chaincode-java/gradlew.bat
vendored
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
5
asset-transfer-events/chaincode-java/settings.gradle
Normal file
5
asset-transfer-events/chaincode-java/settings.gradle
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
rootProject.name = 'asset-transfer-events-java'
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.hyperledger.fabric.samples.events;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import org.hyperledger.fabric.contract.annotation.DataType;
|
||||
import org.hyperledger.fabric.contract.annotation.Property;
|
||||
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
@DataType()
|
||||
public final class Asset {
|
||||
|
||||
@Property()
|
||||
private final String assetID;
|
||||
|
||||
@Property()
|
||||
private String color;
|
||||
|
||||
@Property()
|
||||
private int size;
|
||||
|
||||
@Property()
|
||||
private String owner;
|
||||
|
||||
@Property()
|
||||
private int appraisedValue;
|
||||
|
||||
public Asset(final String assetID, final String color,
|
||||
final int size, final String owner, final int value) {
|
||||
|
||||
this.assetID = assetID;
|
||||
this.color = color;
|
||||
this.size = size;
|
||||
this.owner = owner;
|
||||
this.appraisedValue = value;
|
||||
}
|
||||
|
||||
public String getAssetID() {
|
||||
return assetID;
|
||||
}
|
||||
|
||||
public String getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public String getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public int getAppraisedValue() {
|
||||
return appraisedValue;
|
||||
}
|
||||
|
||||
public void setOwner(final String newowner) {
|
||||
this.owner = newowner;
|
||||
}
|
||||
|
||||
public void setAppraisedValue(final int value) {
|
||||
this.appraisedValue = value;
|
||||
}
|
||||
|
||||
public void setColor(final String c) {
|
||||
this.color = c;
|
||||
}
|
||||
|
||||
public void setSize(final int s) {
|
||||
this.size = s;
|
||||
}
|
||||
|
||||
// Serialize asset without private properties
|
||||
public byte[] serialize() {
|
||||
return serialize(null).getBytes(UTF_8);
|
||||
}
|
||||
|
||||
public String serialize(final String privateProps) {
|
||||
Map<String, Object> tMap = new HashMap();
|
||||
tMap.put("ID", assetID);
|
||||
tMap.put("Color", color);
|
||||
tMap.put("Owner", owner);
|
||||
tMap.put("Size", Integer.toString(size));
|
||||
tMap.put("AppraisedValue", Integer.toString(appraisedValue));
|
||||
if (privateProps != null && privateProps.length() > 0) {
|
||||
tMap.put("asset_properties", new JSONObject(privateProps));
|
||||
}
|
||||
return new JSONObject(tMap).toString();
|
||||
}
|
||||
|
||||
public static Asset deserialize(final byte[] assetJSON) {
|
||||
return deserialize(new String(assetJSON, UTF_8));
|
||||
}
|
||||
|
||||
public static Asset deserialize(final String assetJSON) {
|
||||
|
||||
JSONObject json = new JSONObject(assetJSON);
|
||||
Map<String, Object> tMap = json.toMap();
|
||||
final String id = (String) tMap.get("ID");
|
||||
|
||||
final String color = (String) tMap.get("Color");
|
||||
final String owner = (String) tMap.get("Owner");
|
||||
int size = 0;
|
||||
int appraisedValue = 0;
|
||||
if (tMap.containsKey("Size")) {
|
||||
size = Integer.parseInt((String) tMap.get("Size"));
|
||||
}
|
||||
if (tMap.containsKey("AppraisedValue")) {
|
||||
appraisedValue = Integer.parseInt((String) tMap.get("AppraisedValue"));
|
||||
}
|
||||
return new Asset(id, color, size, owner, appraisedValue);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((obj == null) || (getClass() != obj.getClass())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Asset other = (Asset) obj;
|
||||
|
||||
return Objects.deepEquals(
|
||||
new String[]{getAssetID(), getColor(), getOwner()},
|
||||
new String[]{other.getAssetID(), other.getColor(), other.getOwner()})
|
||||
&&
|
||||
Objects.deepEquals(
|
||||
new int[]{getSize(), getAppraisedValue()},
|
||||
new int[]{other.getSize(), other.getAppraisedValue()});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getAssetID(), getColor(), getSize(), getOwner(), getAppraisedValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + "@" + Integer.toHexString(hashCode())
|
||||
+ " [assetID=" + assetID + ", appraisedValue=" + appraisedValue + ", color="
|
||||
+ color + ", size=" + size + ", owner=" + owner + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.hyperledger.fabric.samples.events;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import org.hyperledger.fabric.contract.Context;
|
||||
import org.hyperledger.fabric.contract.ContractInterface;
|
||||
import org.hyperledger.fabric.contract.annotation.Contact;
|
||||
import org.hyperledger.fabric.contract.annotation.Contract;
|
||||
import org.hyperledger.fabric.contract.annotation.Default;
|
||||
import org.hyperledger.fabric.contract.annotation.Info;
|
||||
import org.hyperledger.fabric.contract.annotation.License;
|
||||
import org.hyperledger.fabric.contract.annotation.Transaction;
|
||||
import org.hyperledger.fabric.shim.ChaincodeException;
|
||||
import org.hyperledger.fabric.shim.ChaincodeStub;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Main Chaincode class.
|
||||
*
|
||||
* @see org.hyperledger.fabric.shim.Chaincode
|
||||
* <p>
|
||||
* Each chaincode transaction function must take, Context as first parameter.
|
||||
* Unless specified otherwise via annotation (@Contract or @Transaction), the contract name
|
||||
* is the class name (without package)
|
||||
* and the transaction name is the method name.
|
||||
*/
|
||||
@Contract(
|
||||
name = "asset-transfer-events-java",
|
||||
info = @Info(
|
||||
title = "Asset Transfer Events",
|
||||
description = "The hyperlegendary asset transfer events sample",
|
||||
version = "0.0.1-SNAPSHOT",
|
||||
license = @License(
|
||||
name = "Apache 2.0 License",
|
||||
url = "http://www.apache.org/licenses/LICENSE-2.0.html"),
|
||||
contact = @Contact(
|
||||
email = "a.transfer@example.com",
|
||||
name = "Fabric Development Team",
|
||||
url = "https://hyperledger.example.com")))
|
||||
@Default
|
||||
public final class AssetTransfer implements ContractInterface {
|
||||
|
||||
static final String IMPLICIT_COLLECTION_NAME_PREFIX = "_implicit_org_";
|
||||
static final String PRIVATE_PROPS_KEY = "asset_properties";
|
||||
|
||||
/**
|
||||
* Retrieves the asset details with the specified ID
|
||||
*
|
||||
* @param ctx the transaction context
|
||||
* @param assetID the ID of the asset
|
||||
* @return the asset found on the ledger. Returns error if asset is not found
|
||||
*/
|
||||
@Transaction(intent = Transaction.TYPE.EVALUATE)
|
||||
public String ReadAsset(final Context ctx, final String assetID) {
|
||||
System.out.printf("ReadAsset: ID %s\n", assetID);
|
||||
|
||||
Asset asset = getState(ctx, assetID);
|
||||
String privData = readPrivateData(ctx, assetID);
|
||||
return asset.serialize(privData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new asset on the ledger. Saves the passed private data (asset properties) from transient map input.
|
||||
*
|
||||
* @param ctx the transaction context
|
||||
* Transient map with asset_properties key with asset json as value
|
||||
* @param assetID
|
||||
* @param color
|
||||
* @param size
|
||||
* @param owner
|
||||
* @param appraisedValue
|
||||
* @return the created asset
|
||||
*/
|
||||
@Transaction(intent = Transaction.TYPE.SUBMIT)
|
||||
public Asset CreateAsset(final Context ctx, final String assetID, final String color, final int size, final String owner, final int appraisedValue) {
|
||||
ChaincodeStub stub = ctx.getStub();
|
||||
//input validations
|
||||
String errorMessage = null;
|
||||
if (assetID == null || assetID.equals("")) {
|
||||
errorMessage = String.format("Empty input: assetID");
|
||||
}
|
||||
if (owner == null || owner.equals("")) {
|
||||
errorMessage = String.format("Empty input: owner");
|
||||
}
|
||||
|
||||
if (errorMessage != null) {
|
||||
System.err.println(errorMessage);
|
||||
throw new ChaincodeException(errorMessage, AssetTransferErrors.INCOMPLETE_INPUT.toString());
|
||||
}
|
||||
// Check if asset already exists
|
||||
byte[] assetJSON = ctx.getStub().getState(assetID);
|
||||
if (assetJSON != null && assetJSON.length > 0) {
|
||||
errorMessage = String.format("Asset %s already exists", assetID);
|
||||
System.err.println(errorMessage);
|
||||
throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_ALREADY_EXISTS.toString());
|
||||
}
|
||||
|
||||
Asset asset = new Asset(assetID, color, size, owner, appraisedValue);
|
||||
|
||||
savePrivateData(ctx, assetID);
|
||||
assetJSON = asset.serialize();
|
||||
System.out.printf("CreateAsset Put: ID %s Data %s\n", assetID, new String(assetJSON));
|
||||
|
||||
stub.putState(assetID, assetJSON);
|
||||
// add Event data to the transaction data. Event will be published after the block containing
|
||||
// this transaction is committed
|
||||
stub.setEvent("CreateAsset", assetJSON);
|
||||
return asset;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TransferAsset transfers the asset to the new owner
|
||||
* Save any private data, if provided in transient map
|
||||
*
|
||||
* @param ctx the transaction context
|
||||
* @param assetID asset to delete
|
||||
* @param newOwner new owner for the asset
|
||||
* @return none
|
||||
*/
|
||||
@Transaction(intent = Transaction.TYPE.SUBMIT)
|
||||
public void TransferAsset(final Context ctx, final String assetID, final String newOwner) {
|
||||
ChaincodeStub stub = ctx.getStub();
|
||||
String errorMessage = null;
|
||||
|
||||
if (assetID == null || assetID.equals("")) {
|
||||
errorMessage = "Empty input: assetID";
|
||||
}
|
||||
if (newOwner == null || newOwner.equals("")) {
|
||||
errorMessage = "Empty input: newOwner";
|
||||
}
|
||||
if (errorMessage != null) {
|
||||
System.err.println(errorMessage);
|
||||
throw new ChaincodeException(errorMessage, AssetTransferErrors.INCOMPLETE_INPUT.toString());
|
||||
}
|
||||
System.out.printf("TransferAsset: verify asset %s exists\n", assetID);
|
||||
Asset thisAsset = getState(ctx, assetID);
|
||||
// Transfer asset to new owner
|
||||
thisAsset.setOwner(newOwner);
|
||||
|
||||
System.out.printf(" Transfer Asset: ID %s to owner %s\n", assetID, newOwner);
|
||||
savePrivateData(ctx, assetID); // save private data if any
|
||||
byte[] assetJSON = thisAsset.serialize();
|
||||
|
||||
stub.putState(assetID, assetJSON);
|
||||
stub.setEvent("TransferAsset", assetJSON); //publish Event
|
||||
}
|
||||
|
||||
/**
|
||||
* Update existing asset on the ledger with provided parameters.
|
||||
* Saves the passed private data (asset properties) from transient map input.
|
||||
*
|
||||
* @param ctx the transaction context
|
||||
* Transient map with asset_properties key with asset json as value
|
||||
* @param assetID
|
||||
* @param color
|
||||
* @param size
|
||||
* @param owner
|
||||
* @param appraisedValue
|
||||
* @return the created asset
|
||||
*/
|
||||
@Transaction(intent = Transaction.TYPE.SUBMIT)
|
||||
public Asset UpdateAsset(final Context ctx, final String assetID, final String color, final int size, final String owner, final int appraisedValue) {
|
||||
ChaincodeStub stub = ctx.getStub();
|
||||
//input validations
|
||||
String errorMessage = null;
|
||||
if (assetID == null || assetID.equals("")) {
|
||||
errorMessage = String.format("Empty input: assetID");
|
||||
}
|
||||
|
||||
if (errorMessage != null) {
|
||||
System.err.println(errorMessage);
|
||||
throw new ChaincodeException(errorMessage, AssetTransferErrors.INCOMPLETE_INPUT.toString());
|
||||
}
|
||||
// reads from the Statedb. Check if asset already exists
|
||||
Asset asset = getState(ctx, assetID);
|
||||
|
||||
if (owner != null) {
|
||||
asset.setOwner(owner);
|
||||
}
|
||||
if (color != null) {
|
||||
asset.setColor(color);
|
||||
}
|
||||
if (size > 0) {
|
||||
asset.setSize(size);
|
||||
}
|
||||
if (appraisedValue > 0) {
|
||||
asset.setAppraisedValue(appraisedValue);
|
||||
}
|
||||
|
||||
savePrivateData(ctx, assetID);
|
||||
byte[] assetJSON = asset.serialize();
|
||||
System.out.printf("UpdateAsset Put: ID %s Data %s\n", assetID, new String(assetJSON));
|
||||
stub.putState(assetID, assetJSON);
|
||||
stub.setEvent("UpdateAsset", assetJSON); //publish Event
|
||||
return asset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a asset & related details from the ledger.
|
||||
*
|
||||
* @param ctx the transaction context
|
||||
* @param assetID asset to delete
|
||||
*/
|
||||
@Transaction(intent = Transaction.TYPE.SUBMIT)
|
||||
public void DeleteAsset(final Context ctx, final String assetID) {
|
||||
ChaincodeStub stub = ctx.getStub();
|
||||
System.out.printf("DeleteAsset: verify asset %s exists\n", assetID);
|
||||
Asset asset = getState(ctx, assetID);
|
||||
|
||||
System.out.printf(" DeleteAsset: ID %s\n", assetID);
|
||||
// delete private details of asset
|
||||
removePrivateData(ctx, assetID);
|
||||
stub.delState(assetID); // delete the key from Statedb
|
||||
stub.setEvent("DeleteAsset", asset.serialize()); //publish Event
|
||||
}
|
||||
|
||||
private Asset getState(final Context ctx, final String assetID) {
|
||||
byte[] assetJSON = ctx.getStub().getState(assetID);
|
||||
if (assetJSON == null || assetJSON.length == 0) {
|
||||
String errorMessage = String.format("Asset %s does not exist", assetID);
|
||||
System.err.println(errorMessage);
|
||||
throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_NOT_FOUND.toString());
|
||||
}
|
||||
|
||||
try {
|
||||
Asset asset = Asset.deserialize(assetJSON);
|
||||
return asset;
|
||||
} catch (Exception e) {
|
||||
throw new ChaincodeException("Deserialize error: " + e.getMessage(), AssetTransferErrors.DATA_ERROR.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private String readPrivateData(final Context ctx, final String assetKey) {
|
||||
String peerMSPID = ctx.getStub().getMspId();
|
||||
String clientMSPID = ctx.getClientIdentity().getMSPID();
|
||||
String implicitCollectionName = getCollectionName(ctx);
|
||||
String privData = null;
|
||||
//only if ClientOrgMatchesPeerOrg
|
||||
if (peerMSPID.equals(clientMSPID)) {
|
||||
System.out.printf(" ReadPrivateData from collection %s, ID %s\n", implicitCollectionName, assetKey);
|
||||
byte[] propJSON = ctx.getStub().getPrivateData(implicitCollectionName, assetKey);
|
||||
|
||||
if (propJSON != null && propJSON.length > 0) {
|
||||
privData = new String(propJSON, UTF_8);
|
||||
}
|
||||
}
|
||||
return privData;
|
||||
}
|
||||
|
||||
private void savePrivateData(final Context ctx, final String assetKey) {
|
||||
String peerMSPID = ctx.getStub().getMspId();
|
||||
String clientMSPID = ctx.getClientIdentity().getMSPID();
|
||||
String implicitCollectionName = getCollectionName(ctx);
|
||||
|
||||
if (peerMSPID.equals(clientMSPID)) {
|
||||
Map<String, byte[]> transientMap = ctx.getStub().getTransient();
|
||||
if (transientMap != null && transientMap.containsKey(PRIVATE_PROPS_KEY)) {
|
||||
byte[] transientAssetJSON = transientMap.get(PRIVATE_PROPS_KEY);
|
||||
|
||||
System.out.printf("Asset's PrivateData Put in collection %s, ID %s\n", implicitCollectionName, assetKey);
|
||||
ctx.getStub().putPrivateData(implicitCollectionName, assetKey, transientAssetJSON);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removePrivateData(final Context ctx, final String assetKey) {
|
||||
String peerMSPID = ctx.getStub().getMspId();
|
||||
String clientMSPID = ctx.getClientIdentity().getMSPID();
|
||||
String implicitCollectionName = getCollectionName(ctx);
|
||||
|
||||
if (peerMSPID.equals(clientMSPID)) {
|
||||
System.out.printf("PrivateData Delete from collection %s, ID %s\n", implicitCollectionName, assetKey);
|
||||
ctx.getStub().delPrivateData(implicitCollectionName, assetKey);
|
||||
}
|
||||
}
|
||||
|
||||
// Return the implicit collection name, to use for private property persistance
|
||||
private String getCollectionName(final Context ctx) {
|
||||
// Get the MSP ID of submitting client identity
|
||||
String clientMSPID = ctx.getClientIdentity().getMSPID();
|
||||
String collectionName = IMPLICIT_COLLECTION_NAME_PREFIX + clientMSPID;
|
||||
return collectionName;
|
||||
}
|
||||
|
||||
private enum AssetTransferErrors {
|
||||
INCOMPLETE_INPUT,
|
||||
INVALID_ACCESS,
|
||||
ASSET_NOT_FOUND,
|
||||
ASSET_ALREADY_EXISTS,
|
||||
DATA_ERROR
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue