diff --git a/token-erc-1155/chaincode-java/Dockerfile b/token-erc-1155/chaincode-java/Dockerfile
new file mode 100755
index 00000000..f46f5b49
--- /dev/null
+++ b/token-erc-1155/chaincode-java/Dockerfile
@@ -0,0 +1,31 @@
+#SPDX-License-Identifier: Apache-2.0
+# the first stage
+FROM gradle:jdk11 AS GRADLE_BUILD
+
+# copy the build.gradle and src code to the container
+COPY src/ src/
+COPY build.gradle ./
+
+# Build and package our code
+RUN gradle --no-daemon build shadowJar -x checkstyleMain -x checkstyleTest
+
+# the second stage of our build just needs the compiled files
+FROM openjdk:11-jre
+ARG CC_SERVER_PORT=9999
+
+# Setup tini to work better handle signals
+ENV TINI_VERSION v0.19.0
+ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
+RUN chmod +x /tini
+
+RUN addgroup --system javauser && useradd -g javauser javauser
+
+# copy only the artifacts we need from the first stage and discard the rest
+COPY --chown=javauser:javauser --from=GRADLE_BUILD /home/gradle/build/libs/chaincode.jar /chaincode.jar
+COPY --chown=javauser:javauser docker/docker-entrypoint.sh /docker-entrypoint.sh
+RUN chmod +x /docker-entrypoint.sh
+ENV PORT $CC_SERVER_PORT
+EXPOSE $CC_SERVER_PORT
+
+USER javauser
+ENTRYPOINT [ "/tini", "--", "/docker-entrypoint.sh" ]
diff --git a/token-erc-1155/chaincode-java/build.gradle b/token-erc-1155/chaincode-java/build.gradle
new file mode 100755
index 00000000..dd0f5c41
--- /dev/null
+++ b/token-erc-1155/chaincode-java/build.gradle
@@ -0,0 +1,91 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+plugins {
+ id 'com.github.johnrengelman.shadow' version '5.1.0'
+ id 'application'
+ id 'checkstyle'
+ id 'jacoco'
+}
+
+group 'org.hyperledger.fabric.samples'
+version '1.0-SNAPSHOT'
+
+dependencies {
+
+ implementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.4.1'
+ implementation 'org.json:json:+'
+ implementation 'com.owlike:genson:1.5'
+ testImplementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.4.1'
+ testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2'
+ testImplementation 'org.assertj:assertj-core:3.11.1'
+ testImplementation 'org.mockito:mockito-core:2.23.4'
+ testRuntimeOnly("net.bytebuddy:byte-buddy:1.10.6")
+
+}
+
+repositories {
+ mavenCentral()
+ maven {
+ url 'https://jitpack.io'
+ }
+}
+
+application {
+ mainClass = '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
+}
+
+jacocoTestCoverageVerification {
+ violationRules {
+ rule {
+ limit {
+ minimum = 0.8
+ }
+ }
+ }
+
+ finalizedBy jacocoTestReport
+}
+
+test {
+ useJUnitPlatform()
+ testLogging {
+ showStandardStreams = true
+ }
+ testLogging {
+ events "passed", "skipped", "failed"
+ }
+}
+
+mainClassName = 'org.hyperledger.fabric.contract.ContractRouter'
+
+shadowJar {
+ baseName = 'chaincode'
+ version = null
+ classifier = null
+
+ manifest {
+ attributes 'Main-Class': 'org.hyperledger.fabric.contract.ContractRouter'
+ }
+}
+
+check.dependsOn jacocoTestCoverageVerification
+installDist.dependsOn check
diff --git a/token-erc-1155/chaincode-java/config/checkstyle/checkstyle.xml b/token-erc-1155/chaincode-java/config/checkstyle/checkstyle.xml
new file mode 100755
index 00000000..deafca76
--- /dev/null
+++ b/token-erc-1155/chaincode-java/config/checkstyle/checkstyle.xml
@@ -0,0 +1,174 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/token-erc-1155/chaincode-java/config/checkstyle/suppressions.xml b/token-erc-1155/chaincode-java/config/checkstyle/suppressions.xml
new file mode 100755
index 00000000..f3b0387f
--- /dev/null
+++ b/token-erc-1155/chaincode-java/config/checkstyle/suppressions.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/token-erc-1155/chaincode-java/docker/docker-entrypoint.sh b/token-erc-1155/chaincode-java/docker/docker-entrypoint.sh
new file mode 100755
index 00000000..1ff8e07e
--- /dev/null
+++ b/token-erc-1155/chaincode-java/docker/docker-entrypoint.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+set -euo pipefail
+: ${CORE_PEER_TLS_ENABLED:="false"}
+: ${DEBUG:="false"}
+
+if [ "${DEBUG,,}" = "true" ]; then
+ exec java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8000 -jar /chaincode.jar
+elif [ "${CORE_PEER_TLS_ENABLED,,}" = "true" ]; then
+ exec java -jar /chaincode.jar # todo
+else
+ exec java -jar /chaincode.jar
+fi
+
diff --git a/token-erc-1155/chaincode-java/gradle/wrapper/gradle-wrapper.jar b/token-erc-1155/chaincode-java/gradle/wrapper/gradle-wrapper.jar
new file mode 100755
index 00000000..7454180f
Binary files /dev/null and b/token-erc-1155/chaincode-java/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/token-erc-1155/chaincode-java/gradle/wrapper/gradle-wrapper.properties b/token-erc-1155/chaincode-java/gradle/wrapper/gradle-wrapper.properties
new file mode 100755
index 00000000..2e6e5897
--- /dev/null
+++ b/token-erc-1155/chaincode-java/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/token-erc-1155/chaincode-java/gradlew b/token-erc-1155/chaincode-java/gradlew
new file mode 100755
index 00000000..1b6c7873
--- /dev/null
+++ b/token-erc-1155/chaincode-java/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original 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 POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${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 "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# 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 ;; #(
+ MSYS* | 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" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/token-erc-1155/chaincode-java/gradlew.bat b/token-erc-1155/chaincode-java/gradlew.bat
new file mode 100755
index 00000000..9618d8d9
--- /dev/null
+++ b/token-erc-1155/chaincode-java/gradlew.bat
@@ -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
diff --git a/token-erc-1155/chaincode-java/settings.gradle b/token-erc-1155/chaincode-java/settings.gradle
new file mode 100755
index 00000000..ba793b99
--- /dev/null
+++ b/token-erc-1155/chaincode-java/settings.gradle
@@ -0,0 +1,5 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+rootProject.name = 'erc1155'
diff --git a/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/ContractConstants.java b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/ContractConstants.java
new file mode 100755
index 00000000..30a53182
--- /dev/null
+++ b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/ContractConstants.java
@@ -0,0 +1,31 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.hyperledger.fabric.samples.erc1155;
+
+public enum ContractConstants {
+ URI_KEY("uri"),
+ BALANCE_PREFIX("account~tokenId~sender"),
+ APPROVAL_PREFIX("account~operator"),
+ NAME_KEY("name"),
+ SYMBOL_KEY("symbol"),
+ APPROVE_FOR_ALL("ApproveForAll"),
+ TRANSFER("Transfer"),
+ MINTER_ORG_MSP("Org1MSP"),
+ ZERO_ADDRESS("0x0"),
+ TRANSFER_SINGLE_EVENT("TransferSingle"),
+ TRANSFER_BATCH_EVENT("TransferBatch"),
+ TRANSFER_BATCH_MULTI_RECIPIENT_EVENT("TransferBatchMultiRecipient"),
+ APPROVE_FOR_ALL_EVENT("ApprovalForAll");
+
+ private final String value;
+
+ ContractConstants(final String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return this.value;
+ }
+}
diff --git a/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/ContractErrors.java b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/ContractErrors.java
new file mode 100755
index 00000000..b8c41f71
--- /dev/null
+++ b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/ContractErrors.java
@@ -0,0 +1,19 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.fabric.samples.erc1155;
+
+public enum ContractErrors {
+ TOKEN_NOT_FOUND,
+ TOKEN_ALREADY_EXITS,
+ NO_OWNER_ASSIGNED,
+ UNAUTHORIZED_SENDER,
+ INVALID_TOKEN_OWNER,
+ NOT_FOUND,
+ INVALID_ADDRESS,
+ INVALID_AMOUNT,
+ INVALID_ARGUMENTS,
+ INSUFFICIENT_FUND,
+ INVALID_URI,
+ INVALID_INPUT
+}
diff --git a/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/ERC1155Contract.java b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/ERC1155Contract.java
new file mode 100755
index 00000000..1e0572f3
--- /dev/null
+++ b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/ERC1155Contract.java
@@ -0,0 +1,993 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.hyperledger.fabric.samples.erc1155;
+
+import com.owlike.genson.Genson;
+import org.hyperledger.fabric.Logger;
+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.samples.erc1155.models.ApprovalForAll;
+import org.hyperledger.fabric.samples.erc1155.models.ToID;
+import org.hyperledger.fabric.samples.erc1155.models.TransferBatch;
+import org.hyperledger.fabric.samples.erc1155.models.TransferBatchMultiRecipient;
+import org.hyperledger.fabric.samples.erc1155.models.TransferSingle;
+import org.hyperledger.fabric.shim.ChaincodeException;
+import org.hyperledger.fabric.shim.ChaincodeStub;
+import org.hyperledger.fabric.shim.ledger.CompositeKey;
+import org.hyperledger.fabric.shim.ledger.KeyValue;
+import org.hyperledger.fabric.shim.ledger.QueryResultsIterator;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.hyperledger.fabric.samples.erc1155.utils.ContractUtility.stringIsNullOrEmpty;
+
+@Contract(
+ name = "erc1155token",
+ info =
+ @Info(
+ title = "ERC1155Token Contract",
+ description = "The erc1155 multi token standard implementation",
+ 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 = "renjith.narayanan@gmail.com",
+ name = "Renjith Narayanan",
+ url = "https://hyperledger.example.com")))
+@Default
+public class ERC1155Contract implements ContractInterface {
+
+ final Logger logger = Logger.getLogger(ERC1155Contract.class);
+
+ /**
+ * Mint a new non-fungible token
+ *
+ * @param ctx the transaction context
+ * @param tokenId Unique ID of the non-fungible token to be minted
+ * @param tokenURI URI containing metadata of the minted non-fungible token
+ * @return Return the non-fungible token object
+ */
+ @Transaction(intent = Transaction.TYPE.SUBMIT)
+ public void Mint(final Context ctx, final String account, final long id, final long amount) {
+
+ // check if contract has been initialized first
+ this.checkInitialized(ctx);
+ // Check minter authorization - this sample assumes Org1 is the central banker with privilege to
+ // mint new tokens
+ this.authorizationHelper(ctx);
+ // Get ID of submitting client identity
+ String operator = ctx.getClientIdentity().getId();
+ // Mint tokens
+ this.mintHelper(ctx, operator, account, id, amount);
+ // Emit TransferSingle event
+ TransferSingle transferSingleEvent =
+ new TransferSingle(
+ operator, ContractConstants.ZERO_ADDRESS.getValue(), account, id, amount);
+ this.emitTransferSingle(ctx, transferSingleEvent);
+ }
+
+ /**
+ * MintBatch creates amount tokens for each token type id and assigns them to account This
+ * function emits a TransferBatch event
+ *
+ * @param ctx the transaction context
+ * @param tokenId Unique ID of the non-fungible token to be minted
+ * @param tokenURI URI containing metadata of the minted non-fungible token
+ * @return Return the non-fungible token object
+ */
+ @Transaction(intent = Transaction.TYPE.SUBMIT)
+ public void MintBatch(
+ final Context ctx, final String account, final long[] ids, final long[] amounts) {
+
+ // check if contract has been initialized first
+ this.checkInitialized(ctx);
+
+ if (ids.length != amounts.length) {
+
+ throw new ChaincodeException(
+ "ids and amounts must have the same length", ContractErrors.INVALID_ARGUMENTS.toString());
+ }
+
+ // Check minter authorization - this sample assumes Org1 is the central banker with privilege to
+ // mint new tokens
+ this.authorizationHelper(ctx);
+
+ // Get ID of submitting client identity
+ String operator = ctx.getClientIdentity().getId();
+
+ // Group amount by token id because we can only send token to a recipient only one time in a
+ // block. This prevents key conflicts
+ Map amountToSend = new HashMap<>(); // token id => amount
+
+ for (int i = 0; i < amounts.length; i++) {
+ if (amountToSend.containsKey(ids[i])) {
+ amountToSend.put(ids[i], Math.addExact(amountToSend.get(ids[i]).longValue(), amounts[i]));
+ } else {
+ amountToSend.put(ids[i], amounts[i]);
+ }
+ }
+
+ // Copy the map keys and sort it. This is necessary because iterating maps in Go is not
+ // deterministic
+ List amountToSendKeys = this.sortedKeys(amountToSend);
+
+ // Mint tokens
+ for (long id : amountToSendKeys) {
+ long amount = amountToSend.get(id);
+ this.mintHelper(ctx, operator, account, id, amount);
+ }
+
+ // Emit TransferBatch event
+ TransferBatch transferBatchEvent =
+ new TransferBatch(
+ operator, ContractConstants.ZERO_ADDRESS.getValue(), account, ids, amounts);
+ this.emitTransferBatch(ctx, transferBatchEvent);
+ }
+
+ /**
+ * Burn destroys amount tokens of token type id from account. This function triggers a
+ * TransferSingle event.
+ *
+ * @param ctx the transaction context
+ * @param account the account from where to burn token
+ * @param id token id
+ * @param amount amount of tokens to burn for the given token id.
+ */
+ @Transaction(intent = Transaction.TYPE.SUBMIT)
+ public void Burn(final Context ctx, final String account, final long id, final long amount) {
+ // check if contract has been initialized first
+ this.checkInitialized(ctx);
+ if (account.equalsIgnoreCase(ContractConstants.ZERO_ADDRESS.getValue())) {
+ throw new ChaincodeException(
+ "burn to the zero address", ContractErrors.INVALID_ADDRESS.toString());
+ }
+ // Check minter authorization - this sample assumes Org1 is the central banker with privilege to
+ // burn new tokens
+ this.authorizationHelper(ctx);
+ // Get ID of submitting client identity
+ String operator = ctx.getClientIdentity().getId();
+ // Burn tokens
+ this.removeBalance(ctx, account, new long[] {id}, new long[] {amount});
+ TransferSingle transferSingleEvent =
+ new TransferSingle(
+ operator, account, ContractConstants.ZERO_ADDRESS.getValue(), id, amount);
+ this.emitTransferSingle(ctx, transferSingleEvent);
+ }
+
+ /**
+ * Batched version of Burn.
+ *
+ * @param ctx the transaction context
+ * @param account the account from where to burn token
+ * @param ids array of token ids
+ * @param amounts array of amount to burn corresponds to the token ids
+ */
+ @Transaction(intent = Transaction.TYPE.SUBMIT)
+ public void BurnBatch(
+ final Context ctx, final String account, final long[] ids, final long[] amounts) {
+
+ // check if contract has been initialized first
+ this.checkInitialized(ctx);
+ if (account.equalsIgnoreCase(ContractConstants.ZERO_ADDRESS.getValue())) {
+ throw new ChaincodeException(
+ "burn to the zero address", ContractErrors.INVALID_ADDRESS.toString());
+ }
+ if (ids.length != amounts.length) {
+ throw new ChaincodeException(
+ "ids and amounts must have the same length", ContractErrors.INVALID_INPUT.toString());
+ }
+
+ // Check minter authorization - this sample assumes Org1 is the central banker with privilege to
+ // burn new tokens
+ this.authorizationHelper(ctx);
+
+ // Get ID of submitting client identity
+ String operator = ctx.getClientIdentity().getId();
+ this.removeBalance(ctx, account, ids, amounts);
+ TransferBatch transferBatchEvent =
+ new TransferBatch(
+ operator, account, ContractConstants.ZERO_ADDRESS.getValue(), ids, amounts);
+ this.emitTransferBatch(ctx, transferBatchEvent);
+ }
+
+ /**
+ * TransferFrom transfers tokens from sender account to recipient account. recipient account must
+ * be a valid clientID as returned by the ClientID() function This function triggers a
+ * TransferSingle event
+ *
+ * @param ctx
+ * @param sender
+ * @param recipient
+ * @param id
+ * @param amount
+ */
+ @Transaction(intent = Transaction.TYPE.SUBMIT)
+ public void TransferFrom(
+ final Context ctx,
+ final String sender,
+ final String recipient,
+ final long id,
+ final long amount) {
+
+ // check if contract has been initialized first
+ this.checkInitialized(ctx);
+
+ if (sender.equalsIgnoreCase(recipient)) {
+
+ throw new ChaincodeException("transfer to self", ContractErrors.INVALID_INPUT.toString());
+ }
+
+ if (recipient.equalsIgnoreCase(ContractConstants.ZERO_ADDRESS.getValue())) {
+
+ throw new ChaincodeException(
+ "transfer to the zero address", ContractErrors.INVALID_ADDRESS.toString());
+ }
+
+ // Get ID of submitting client identity
+ String operator = ctx.getClientIdentity().getId();
+
+ // Check whether operator is owner or approved
+ if (!operator.equalsIgnoreCase(sender)) {
+ boolean approved = _isApprovedForAll(ctx, sender, operator);
+
+ if (!approved) {
+ throw new ChaincodeException(
+ "caller is not owner nor is approved", ContractErrors.UNAUTHORIZED_SENDER.toString());
+ }
+ }
+
+ // Withdraw the funds from the sender address
+ this.removeBalance(ctx, sender, new long[] {id}, new long[] {amount});
+
+ // Deposit the fund to the recipient address
+ this.addBalance(ctx, sender, recipient, id, amount);
+
+ // Emit TransferSingle event
+ TransferSingle transferSingleEvent =
+ new TransferSingle(operator, sender, recipient, id, amount);
+ this.emitTransferSingle(ctx, transferSingleEvent);
+ }
+
+ /**
+ * BatchTransferFrom transfers multiple tokens from sender account to recipient account recipient
+ * account must be a valid clientID as returned by the ClientID() function This function triggers
+ * a TransferBatch event
+ *
+ * @param ctx the transaction context
+ * @param sender the sender account id
+ * @param recipient account id of the recipient
+ * @param ids token ids
+ * @param amounts amount to transfer for different token ids
+ */
+ @Transaction(intent = Transaction.TYPE.SUBMIT)
+ public void BatchTransferFrom(
+ final Context ctx,
+ final String sender,
+ final String recipient,
+ final long[] ids,
+ final long[] amounts) {
+
+ this.checkInitialized(ctx); // check if contract has been initialized first
+ if (sender.equalsIgnoreCase(recipient)) {
+ throw new ChaincodeException("transfer to self", ContractErrors.INVALID_INPUT.toString());
+ }
+ if (ids.length != amounts.length) {
+
+ throw new ChaincodeException(
+ "ids and amounts must have the same length", ContractErrors.INVALID_INPUT.toString());
+ }
+
+ String operator = ctx.getClientIdentity().getId(); // Get ID of submitting client identity
+
+ // Check whether operator is owner or approved
+ if (!operator.equalsIgnoreCase(sender)) {
+ boolean approved = _isApprovedForAll(ctx, sender, operator);
+
+ if (!approved) {
+
+ throw new ChaincodeException(
+ "caller is not owner nor is approved", ContractErrors.UNAUTHORIZED_SENDER.toString());
+ }
+ }
+
+ if (recipient.equalsIgnoreCase(ContractConstants.ZERO_ADDRESS.getValue())) {
+ throw new ChaincodeException(
+ "transfer to the zero address", ContractErrors.INVALID_ADDRESS.toString());
+ }
+ this.removeBalance(ctx, sender, ids, amounts); // Withdraw the funds from the sender address
+
+ // Group amount by token id because we can only send token to a recipient only one time in a
+ // block. This prevents key conflicts
+ Map amountToSend = new HashMap<>(); // token id => amount
+ for (int i = 0; i < amounts.length; i++) {
+ amountToSend.put(ids[i], amountToSend.getOrDefault(ids[i], 0L) + amounts[i]);
+ }
+ // Copy the map keys and sort it. This is necessary because iterating maps in Go is not
+ // deterministic
+ List amountToSendKeys = sortedKeys(amountToSend);
+ // Deposit the funds to the recipient address
+
+ for (Long id : amountToSendKeys) {
+ long amount = amountToSend.get(id);
+ this.addBalance(ctx, sender, recipient, id, amount);
+ }
+
+ TransferBatch transferBatchEvent = new TransferBatch(operator, sender, recipient, ids, amounts);
+ this.emitTransferBatch(ctx, transferBatchEvent);
+ }
+
+ /**
+ * BatchTransferFromMultiRecipient transfers multiple tokens from sender account to multiple
+ * recipient accounts recipient account must be a valid clientID as returned by the ClientID()
+ * function This function triggers a TransferBatchMultiRecipient event
+ *
+ * @param ctx the transaction context
+ * @param sender account id of the sender
+ * @param recipients account id of the all the recipients
+ * @param ids all the token Ids
+ * @param amounts amount of token for each token id in the id array.
+ */
+ @Transaction(intent = Transaction.TYPE.SUBMIT)
+ public void BatchTransferFromMultiRecipient(
+ final Context ctx,
+ final String sender,
+ final String[] recipients,
+ final long[] ids,
+ final long[] amounts) {
+
+ // check if contract has been initialized first
+ this.checkInitialized(ctx);
+
+ if ((recipients.length != ids.length) || (ids.length != amounts.length)) {
+ throw new ChaincodeException(
+ "recipients, ids, and amounts must have the same length",
+ ContractErrors.INVALID_INPUT.toString());
+ }
+
+ for (String recipient : recipients) {
+ if (sender.equalsIgnoreCase(recipient)) {
+ throw new ChaincodeException("transfer to self", ContractErrors.INVALID_ADDRESS.toString());
+ }
+ }
+
+ // Get ID of submitting client identity
+ String operator = ctx.getClientIdentity().getId();
+
+ // Check whether operator is owner or approved
+ if (!operator.equalsIgnoreCase(sender)) {
+ boolean approved = _isApprovedForAll(ctx, sender, operator);
+
+ if (!approved) {
+ throw new ChaincodeException(
+ "caller is not owner nor is approved", ContractErrors.INVALID_ADDRESS.toString());
+ }
+ }
+
+ // Withdraw the funds from the sender address
+ this.removeBalance(ctx, sender, ids, amounts);
+
+ // Group amount by (recipient, id ) pair because we can only send token to a recipient only one
+ // time in a block. This prevents key conflicts
+ Map amountToSend = new HashMap<>(); // (recipient, id ) => amount
+ for (int i = 0; i < amounts.length; i++) {
+ ToID key = new ToID(recipients[i], ids[i]);
+ amountToSend.put(key, amountToSend.getOrDefault(key, 0L) + amounts[i]);
+ }
+
+ // Copy the map keys and sort it. This is necessary because iterating maps in Go is not
+ // deterministic
+ List amountToSendKeys = sortedKeysToID(amountToSend);
+
+ // Deposit the funds to the recipient addresses
+ for (ToID amountoSendKey : amountToSendKeys) {
+ if (amountoSendKey.getTo().equalsIgnoreCase(ContractConstants.ZERO_ADDRESS.getValue())) {
+
+ throw new ChaincodeException(
+ "transfer to the zero address", ContractErrors.INVALID_ADDRESS.toString());
+ }
+
+ long amount = amountToSend.get(amountoSendKey);
+
+ this.addBalance(ctx, sender, amountoSendKey.getTo(), amountoSendKey.getId(), amount);
+ }
+
+ // Emit TransferBatchMultiRecipient event
+ TransferBatchMultiRecipient transferBatchMultiRecipientEvent =
+ new TransferBatchMultiRecipient(operator, sender, recipients, ids, amounts);
+ this.emitTransferBatchMultiRecipient(ctx, transferBatchMultiRecipientEvent);
+ }
+
+ /**
+ * IsApprovedForAll returns true if operator is approved to transfer account's tokens.
+ *
+ * @param ctx the transaction context
+ * @param account account id from where token transferred
+ * @param operator the operator of the account
+ * @return
+ */
+ @Transaction(intent = Transaction.TYPE.EVALUATE)
+ public boolean IsApprovedForAll(final Context ctx, final String account, final String operator) {
+ return this._isApprovedForAll(ctx, account, operator);
+ }
+
+ /**
+ * Helper function for IsApprovedForAll
+ *
+ * @param ctx the transaction context
+ * @param account account id from where token transferred
+ * @param operator the operator of the account
+ * @return
+ */
+ private boolean _isApprovedForAll(
+ final Context ctx, final String account, final String operator) {
+
+ // check if contract has been initialized first
+ this.checkInitialized(ctx);
+ CompositeKey approvalKey =
+ ctx.getStub()
+ .createCompositeKey(ContractConstants.APPROVAL_PREFIX.getValue(), account, operator);
+
+ String approved = ctx.getStub().getStringState(approvalKey.toString());
+
+ if (stringIsNullOrEmpty(approved)) {
+ return false;
+ }
+
+ return new Boolean(approved);
+ }
+
+ /**
+ * SetApprovalForAll returns true if operator is approved to transfer account's tokens. Emits an
+ * ApprovalForAll event.
+ *
+ * @param ctx the transaction context
+ * @param operator get the permission to transfer account's tokens.
+ * @param approved
+ */
+ @Transaction(intent = Transaction.TYPE.SUBMIT)
+ public void SetApprovalForAll(final Context ctx, final String operator, final boolean approved) {
+
+ // check if contract has been initialized first
+ this.checkInitialized(ctx);
+
+ // Get ID of submitting client identity
+ String account = ctx.getClientIdentity().getId();
+ if (account.equalsIgnoreCase(operator)) {
+ throw new ChaincodeException(
+ "setting approval status for self", ContractErrors.INVALID_INPUT.toString());
+ }
+
+ CompositeKey approvalKey =
+ ctx.getStub()
+ .createCompositeKey(ContractConstants.APPROVAL_PREFIX.getValue(), account, operator);
+ ctx.getStub().putStringState(approvalKey.toString(), String.valueOf(approved));
+ ApprovalForAll approvalForAllEvent = new ApprovalForAll(account, operator, approved);
+ ctx.getStub()
+ .setEvent(
+ ContractConstants.APPROVE_FOR_ALL_EVENT.getValue(), this.marshal(approvalForAllEvent));
+ }
+
+ /**
+ * BalanceOf returns the balance of the given account
+ *
+ * @param ctx the transaction context
+ * @param account the account id
+ * @param id the given token id
+ * @return
+ */
+ @Transaction(intent = Transaction.TYPE.EVALUATE)
+ public long BalanceOf(final Context ctx, final String account, final long id) {
+
+ // check if contract has been initialized first
+ this.checkInitialized(ctx);
+ return balanceOfHelper(ctx, account, id);
+ }
+
+ /**
+ * BalanceOfBatch returns the balance of multiple account/token pairs
+ *
+ * @param ctx the transaction context
+ * @param accounts the account ids
+ * @param ids the given token ids
+ * @return
+ */
+ @Transaction(intent = Transaction.TYPE.EVALUATE)
+ public long[] BalanceOfBatch(final Context ctx, final String[] accounts, final long[] ids) {
+
+ // check if contract has been initialized first
+ this.checkInitialized(ctx);
+ if (accounts.length != ids.length) {
+ throw new ChaincodeException(
+ "ids and amounts must have the same length", ContractErrors.INVALID_INPUT.toString());
+ }
+
+ long[] balances = new long[accounts.length];
+
+ for (int i = 0; i < accounts.length; i++) {
+
+ balances[i] = balanceOfHelper(ctx, accounts[i], ids[i]);
+ }
+
+ return balances;
+ }
+
+ /**
+ * ClientAccountBalance returns the balance of the requesting client's account
+ *
+ * @param ctx the transaction context
+ * @param id token id
+ * @return
+ */
+ @Transaction(intent = Transaction.TYPE.EVALUATE)
+ public long ClientAccountBalance(final Context ctx, final long id) {
+
+ // check if contract has been initialized first
+ this.checkInitialized(ctx);
+
+ // Get ID of submitting client identity
+ String clientID = ctx.getClientIdentity().getId();
+
+ return balanceOfHelper(ctx, clientID, id);
+ }
+
+ /**
+ * ClientAccountID returns the id of the requesting client's account.In this implementation, the
+ * client account ID is the clientId itself Users can use this function to get their own account
+ * id, which they can then give to others as the payment address.
+ *
+ * @param ctx the transaction context
+ * @return
+ */
+ @Transaction(intent = Transaction.TYPE.EVALUATE)
+ public String ClientAccountID(final Context ctx) {
+ // check if contract has been initialized first
+ this.checkInitialized(ctx);
+ // Get ID of submitting client identity
+ String clientAccountID = ctx.getClientIdentity().getId();
+ return clientAccountID;
+ }
+
+ /**
+ * SetURI set the URI value. This function triggers URI event for each token id .This
+ * implementation returns the same URI for all token types. It relies on the token type ID
+ * substitution mechanism .
+ *
+ * @param ctx the transaction context
+ * @param uri the token URI
+ */
+ @Transaction(intent = Transaction.TYPE.SUBMIT)
+ public void SetURI(final Context ctx, final String uri) {
+
+ // check if contract has been initialized first
+ this.checkInitialized(ctx);
+
+ // Check minter authorization - this sample assumes Org1 is the central banker with privilege to
+ // mint new tokens
+ this.authorizationHelper(ctx);
+ if (uri.indexOf("{id}") < 0) {
+ throw new ChaincodeException(
+ "failed to set uri, uri should contain '{id}'", ContractErrors.INVALID_URI.toString());
+ }
+
+ ctx.getStub().putStringState(ContractConstants.URI_KEY.getValue(), uri);
+ }
+
+ /**
+ * Returns the URI.This implementation returns the same URI for *all* token types. It relies on
+ * the token type ID substitution mechanism. Clients calling this function must replace the
+ * `\{id\}` substring with the actual token type ID.
+ *
+ * @param ctx the transaction context
+ * @param id tokenid
+ * @return
+ */
+ @Transaction(intent = Transaction.TYPE.EVALUATE)
+ public String URI(final Context ctx, final long id) {
+ // check if contract has been initialized first
+ final ChaincodeStub stub = ctx.getStub();
+ String uri = stub.getStringState(ContractConstants.URI_KEY.getValue());
+ if (stringIsNullOrEmpty(uri)) {
+ throw new ChaincodeException("no uri is set", ContractErrors.NOT_FOUND.toString());
+ }
+ return uri;
+ }
+
+ /**
+ * @param ctx
+ * @param id
+ */
+ @Transaction(intent = Transaction.TYPE.EVALUATE)
+ public void BroadcastTokenExistance(final Context ctx, final long id) {
+ // check if contract has been initialized first
+ this.checkInitialized(ctx);
+ // Check minter authorization - this sample assumes Org1 is the central banker with privilege to
+ // mint new tokens
+ this.authorizationHelper(ctx);
+ String operator = ctx.getClientIdentity().getId();
+ TransferSingle transferSingleEvent =
+ new TransferSingle(
+ operator,
+ ContractConstants.ZERO_ADDRESS.getValue(),
+ ContractConstants.ZERO_ADDRESS.getValue(),
+ id,
+ 0);
+ this.emitTransferSingle(ctx, transferSingleEvent);
+ }
+
+ /**
+ * Returns a descriptive name for a collection of non-fungible tokens in this contract
+ *
+ * @param ctx the transaction context
+ * @return name of the token.
+ */
+ @Transaction(intent = Transaction.TYPE.EVALUATE)
+ public String Name(final Context ctx) {
+ // check contract options are already set first to execute the function
+ this.checkInitialized(ctx);
+ return ctx.getStub().getStringState(ContractConstants.NAME_KEY.getValue());
+ }
+
+ /**
+ * Returns an abbreviated symbol for non-fungible tokens in this contract.
+ *
+ * @param ctx the transaction context
+ * @return token symbol.
+ */
+ @Transaction(intent = Transaction.TYPE.EVALUATE)
+ public String Symbol(final Context ctx) {
+ // check contract options are already set first to execute the function
+ this.checkInitialized(ctx);
+ return ctx.getStub().getStringState(ContractConstants.SYMBOL_KEY.getValue());
+ }
+
+ /**
+ * Initialize the token with name and symbol.
+ *
+ * @param ctx the transaction context
+ * @param name token name
+ * @param symbol token symbol
+ */
+ @Transaction(intent = Transaction.TYPE.SUBMIT)
+ public void Initialize(final Context ctx, final String name, final String symbol) {
+
+ final ChaincodeStub stub = ctx.getStub();
+ final String clientMSPID = ctx.getClientIdentity().getMSPID();
+
+ // Check minter authorization - this sample assumes Org1 is the issuer with privilege to set the
+ // name and symbol
+ if (!clientMSPID.equalsIgnoreCase(ContractConstants.MINTER_ORG_MSP.getValue())) {
+ throw new ChaincodeException(
+ "Client is not authorized to initialize the contract",
+ ContractErrors.UNAUTHORIZED_SENDER.toString());
+ }
+
+ // check contract options are not already set, client is not authorized to change them once
+ // initialized
+ String tokenName = stub.getStringState(ContractConstants.NAME_KEY.getValue());
+ if (!stringIsNullOrEmpty(tokenName)) {
+ throw new ChaincodeException(
+ "contract options are already set, client is not authorized to change them",
+ ContractErrors.UNAUTHORIZED_SENDER.toString());
+ }
+ stub.putStringState(ContractConstants.NAME_KEY.getValue(), name);
+ stub.putStringState(ContractConstants.SYMBOL_KEY.getValue(), symbol);
+ }
+
+ /**
+ * This helper function checks minter authorization - this sample assumes Org1 is the central
+ * banker with privilege to mint new tokens Throw exception if not initialized.
+ *
+ * @param ctx the transaction context
+ * @return
+ */
+ private void authorizationHelper(final Context ctx) {
+ String clientMSPID = ctx.getClientIdentity().getMSPID();
+ if (!clientMSPID.equalsIgnoreCase(ContractConstants.MINTER_ORG_MSP.getValue())) {
+ throw new ChaincodeException(
+ "Client is not authorized to set the name and symbol of the token",
+ ContractErrors.UNAUTHORIZED_SENDER.toString());
+ }
+ }
+
+ /**
+ * Update the balance of account for the given token id.
+ *
+ * @param ctx
+ * @param account
+ * @param id
+ * @return
+ */
+ private long balanceOfHelper(final Context ctx, final String account, final long id) {
+
+ if (account.equalsIgnoreCase(ContractConstants.ZERO_ADDRESS.getValue())) {
+ throw new ChaincodeException(
+ "balance query for the zero address", ContractErrors.INVALID_ADDRESS.toString());
+ }
+
+ // Convert id to string
+ String idString = String.valueOf(id);
+ long balance = 0;
+ final QueryResultsIterator balanceIterator =
+ ctx.getStub()
+ .getStateByPartialCompositeKey(
+ ContractConstants.BALANCE_PREFIX.getValue(), account, idString);
+ for (KeyValue keyVal : balanceIterator) {
+ long balAmount = Long.parseLong(keyVal.getStringValue());
+ balance = Math.addExact(balance, balAmount);
+ }
+ return balance;
+ }
+
+ /**
+ * Helper function to add balance of recipient account
+ *
+ * @param ctx the transaction context
+ * @param sender the sender account id
+ * @param recipient the token recipient account id
+ * @param id the token id
+ * @param amount the amount of token
+ */
+ private void addBalance(
+ final Context ctx,
+ final String sender,
+ final String recipient,
+ final long id,
+ final long amount) {
+ ChaincodeStub stub = ctx.getStub();
+ String idString = String.valueOf(id); // Convert id to string
+ CompositeKey balanceKey =
+ stub.createCompositeKey(
+ ContractConstants.BALANCE_PREFIX.getValue(), recipient, idString, sender);
+ String balanceStr = stub.getStringState(balanceKey.toString());
+ long balance = 0;
+ if (!stringIsNullOrEmpty(balanceStr)) {
+
+ balance = Long.parseLong(balanceStr);
+ }
+ balance = Math.addExact(balance, amount);
+ stub.putStringState(balanceKey.toString(), String.valueOf(balance));
+ logger.info(String.format("account %s balance updated to %d", recipient, balance));
+ }
+
+ /**
+ * Remove Balance of the sender for each given token id and amounts.
+ *
+ * @param ctx the transaction context
+ * @param sender the owner of the token
+ * @param ids each token ids
+ * @param amounts the amount of the token to transfered for each id
+ */
+ private void removeBalance(
+ final Context ctx, final String sender, final long[] ids, final long[] amounts) {
+
+ // Calculate the total amount of each token to withdraw
+ Map necessaryFunds = new HashMap(); // token id -> necessary amount
+ for (int i = 0; i < amounts.length; i++) {
+ necessaryFunds.put(ids[i], necessaryFunds.getOrDefault(i, 0L) + amounts[i]);
+ }
+ ChaincodeStub stub = ctx.getStub();
+ // Copy the map keys and sort it. This is necessary because iterating maps in Go is not
+ // deterministic
+ List necessaryFundsKeys = sortedKeys(necessaryFunds);
+ // Check whether the sender has the necessary funds and withdraw them from the account
+ for (long tokenId : necessaryFundsKeys) {
+ long neededAmount = necessaryFunds.get(tokenId);
+ String idString = String.valueOf(tokenId);
+ long partialBalance = 0;
+ boolean selfRecipientKeyNeedsToBeRemoved = false;
+ String selfRecipientKey = "";
+ final QueryResultsIterator balanceResulutIterator =
+ ctx.getStub()
+ .getStateByPartialCompositeKey(
+ ContractConstants.BALANCE_PREFIX.getValue(), sender, idString);
+ Iterator balanceIterator = balanceResulutIterator.iterator();
+ while (balanceIterator.hasNext() && partialBalance < neededAmount) {
+ KeyValue queryResponse = balanceIterator.next();
+ long partBalAmount = Long.valueOf(queryResponse.getStringValue());
+ partialBalance = Math.addExact(partialBalance, partBalAmount);
+ CompositeKey compositeKeyParts = stub.splitCompositeKey(queryResponse.getKey());
+ if (compositeKeyParts.getAttributes().contains(sender)) {
+ selfRecipientKeyNeedsToBeRemoved = true;
+ selfRecipientKey = queryResponse.getKey();
+ } else {
+ ctx.getStub().delState(queryResponse.getKey());
+ }
+ }
+ if (partialBalance < neededAmount) {
+ String error =
+ String.format(
+ "sender has insufficient funds for token %s, needed funds: %d, available fund: %d",
+ tokenId, neededAmount, partialBalance);
+ throw new ChaincodeException(error, ContractErrors.INSUFFICIENT_FUND.toString());
+
+ } else if (partialBalance > neededAmount) {
+
+ // Send the remainder back to the sender
+ long remainder = Math.subtractExact(partialBalance, neededAmount);
+
+ if (selfRecipientKeyNeedsToBeRemoved) {
+
+ // Set balance for the key that has the same address for sender and recipient
+ this.setBalance(ctx, sender, sender, tokenId, remainder);
+
+ } else {
+
+ this.addBalance(ctx, sender, sender, tokenId, remainder);
+ }
+
+ } else {
+
+ // Delete self recipient key
+ stub.delState(selfRecipientKey);
+ }
+ }
+ }
+
+ /**
+ * Helper function for token mint call.
+ *
+ * @param ctx the transaction context
+ * @param operator the approved operator of the token
+ * @param account account id updated with token
+ * @param id the token id
+ * @param amount quantity of token to be minted
+ */
+ private void mintHelper(
+ final Context ctx,
+ final String operator,
+ final String account,
+ final long id,
+ final long amount) {
+
+ if (account.equalsIgnoreCase(ContractConstants.ZERO_ADDRESS.getValue())) {
+
+ throw new ChaincodeException(
+ "mint to the zero address", ContractErrors.INVALID_ADDRESS.toString());
+ }
+
+ if (amount <= 0) {
+
+ throw new ChaincodeException(
+ "Mint amount must be a positive integer", ContractErrors.INVALID_AMOUNT.toString());
+ }
+
+ this.addBalance(ctx, operator, account, id, amount);
+ }
+
+ private void setBalance(
+ final Context ctx,
+ final String sender,
+ final String recipient,
+ final long id,
+ final long amount) {
+ // Convert id to string
+ String idString = String.valueOf(id);
+ CompositeKey balanceKey =
+ ctx.getStub()
+ .createCompositeKey(
+ ContractConstants.BALANCE_PREFIX.getValue(), recipient, idString, sender);
+ ctx.getStub().putStringState(balanceKey.toString(), String.valueOf(amount));
+ }
+
+ /**
+ * Emit when single recipient token transfer occur.
+ *
+ * @param ctx
+ * @param transferSingleEvent
+ */
+ private void emitTransferSingle(final Context ctx, final TransferSingle transferSingleEvent) {
+
+ ctx.getStub()
+ .setEvent(
+ ContractConstants.TRANSFER_SINGLE_EVENT.getValue(), this.marshal(transferSingleEvent));
+ }
+
+ /**
+ * Emit transfer batch event.
+ *
+ * @param ctx the transaction context
+ * @param transferBatchEvent BatchEvent details
+ */
+ private void emitTransferBatch(final Context ctx, final TransferBatch transferBatchEvent) {
+
+ ctx.getStub()
+ .setEvent(
+ ContractConstants.TRANSFER_BATCH_EVENT.getValue(), this.marshal(transferBatchEvent));
+ }
+
+ /**
+ * EmitTransferBatchMultiRecipient when multi recipient token batch transfer occurs.
+ *
+ * @param ctx the transaction context
+ * @param transferBatchMutipleRecipientEvent event details
+ */
+ private void emitTransferBatchMultiRecipient(
+ final Context ctx, final TransferBatchMultiRecipient transferBatchMutipleRecipientEvent) {
+
+ ctx.getStub()
+ .setEvent(
+ ContractConstants.TRANSFER_BATCH_MULTI_RECIPIENT_EVENT.getValue(),
+ this.marshal(transferBatchMutipleRecipientEvent));
+ }
+
+ /**
+ * Returns the sorted array from the keys of map
+ *
+ * @param map the keys of map data to be sorted
+ * @return
+ */
+ private List sortedKeys(final Map map) {
+ List sortedKeys = new ArrayList<>();
+ sortedKeys.addAll(map.keySet());
+ Collections.sort(sortedKeys);
+ return sortedKeys;
+ }
+
+ /**
+ * Returns the sorted slice ([]ToID) copied from the keys of map[ToID]uint64
+ *
+ * @param the keys of map data to be sorted
+ * @return
+ */
+ private List sortedKeysToID(final Map map) {
+
+ List sortedKeys = new ArrayList<>();
+ sortedKeys.addAll(map.keySet());
+ Collections.sort(
+ sortedKeys,
+ new Comparator() {
+ public int compare(final ToID toId1, final ToID toId2) {
+ if (toId1.getId() != toId2.getId()) {
+ return toId1.getTo().compareTo(toId2.getTo());
+ }
+
+ return new Long(toId1.getId()).compareTo(toId2.getId());
+ }
+ });
+
+ return sortedKeys;
+ }
+
+ /**
+ * Checks that contract options have been already initialized Throw exception if not initialized.
+ *
+ * @param ctx the transaction context
+ * @return
+ */
+ private void checkInitialized(final Context ctx) {
+ String tokenName = ctx.getStub().getStringState(ContractConstants.NAME_KEY.getValue());
+ if (stringIsNullOrEmpty(tokenName)) {
+ throw new ChaincodeException(
+ "Contract options need to be set before calling any function, call Initialize() to initialize contract",
+ ContractErrors.TOKEN_NOT_FOUND.toString());
+ }
+ }
+
+ /**
+ * marshal the event data
+ *
+ * @param obj the object to marshal.
+ * @return marshalled object.
+ */
+ private byte[] marshal(final Object obj) {
+ return new Genson().serialize(obj).getBytes(UTF_8);
+ }
+}
diff --git a/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/ApprovalForAll.java b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/ApprovalForAll.java
new file mode 100755
index 00000000..642bc704
--- /dev/null
+++ b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/ApprovalForAll.java
@@ -0,0 +1,68 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.fabric.samples.erc1155.models;
+
+import com.owlike.genson.annotation.JsonProperty;
+import org.hyperledger.fabric.contract.annotation.Property;
+
+public class ApprovalForAll {
+
+ @Property()
+ @JsonProperty("owner")
+ private String owner;
+
+ @Property()
+ @JsonProperty("operator")
+ private String operator;
+
+ @Property()
+ @JsonProperty("approved")
+ private boolean approved;
+
+ /** Default constructor */
+ public ApprovalForAll() {
+ super();
+ }
+
+ /**
+ * Constructor of the class
+ *
+ * @param owner
+ * @param operator
+ * @param approved
+ */
+ public ApprovalForAll(final String owner, final String operator, final boolean approved) {
+ super();
+ this.owner = owner;
+ this.operator = operator;
+ this.approved = approved;
+ }
+
+ /**
+ * getter function for the owner
+ *
+ * @return the token owner account
+ */
+ public String getOwner() {
+ return owner;
+ }
+
+ /**
+ * getter function for operator
+ *
+ * @return the token approved operator
+ */
+ public String getOperator() {
+ return operator;
+ }
+
+ /**
+ * getter function for the approved
+ *
+ * @return is operator is approved one or not
+ */
+ public boolean getApproved() {
+ return approved;
+ }
+}
diff --git a/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/Event.java b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/Event.java
new file mode 100755
index 00000000..112b4e4d
--- /dev/null
+++ b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/Event.java
@@ -0,0 +1,68 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.fabric.samples.erc1155.models;
+
+import com.owlike.genson.annotation.JsonProperty;
+import org.hyperledger.fabric.contract.annotation.Property;
+
+public abstract class Event {
+
+ @Property()
+ @JsonProperty("operator")
+ private String operator;
+
+ @Property()
+ @JsonProperty("from")
+ private String from;
+
+ @Property()
+ @JsonProperty("to")
+ private String to;
+
+ /** Default constructor */
+ public Event() {
+ super();
+ }
+
+ /**
+ * Constructor of the class
+ *
+ * @param operator
+ * @param from
+ * @param to
+ */
+ public Event(final String operator, final String from, final String to) {
+ super();
+ this.operator = operator;
+ this.from = from;
+ this.to = to;
+ }
+
+ /**
+ * getter function for the operator
+ *
+ * @return the token operator
+ */
+ public String getOperator() {
+ return operator;
+ }
+
+ /**
+ * getter function for the from
+ *
+ * @return the sender account
+ */
+ public String getFrom() {
+ return from;
+ }
+
+ /**
+ * getter function for the to
+ *
+ * @return the recipient account
+ */
+ public String getTo() {
+ return to;
+ }
+}
diff --git a/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/ToID.java b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/ToID.java
new file mode 100755
index 00000000..dc4a35f9
--- /dev/null
+++ b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/ToID.java
@@ -0,0 +1,98 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.fabric.samples.erc1155.models;
+
+import org.hyperledger.fabric.contract.annotation.Property;
+
+import com.owlike.genson.annotation.JsonProperty;
+
+public class ToID {
+
+ @Property()
+ @JsonProperty("id")
+ private long id;
+
+ @Property()
+ @JsonProperty("to")
+ private String to;
+
+ /** Default constructor */
+ public ToID() {
+ super();
+ }
+
+ /**
+ * Constructor of the class
+ *
+ * @param id tokenId
+ * @param to the recipient account
+ */
+ public ToID(final String to, final long id) {
+ super();
+ this.to = to;
+ this.id = id;
+ }
+
+ /**
+ * The getter function for the Id
+ *
+ * @return the tokenId
+ */
+ public long getId() {
+ return id;
+ }
+
+ /**
+ * The getter function for the To.
+ *
+ * @return the recipient account id
+ */
+ public String getTo() {
+ return to;
+ }
+
+ /**
+ *
+ * @param obj
+ * @return
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ ToID other = (ToID) obj;
+ if (id != other.id) {
+ return false;
+ }
+ if (to == null) {
+ if (other.to != null) {
+ return false;
+ }
+ } else if (!to.equalsIgnoreCase(other.to)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ *
+ * @return the hashcode
+ */
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (int) (id ^ (id >>> 32));
+ result = prime * result + ((to == null) ? 0 : to.hashCode());
+ return result;
+ }
+}
diff --git a/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/TransferBatch.java b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/TransferBatch.java
new file mode 100755
index 00000000..bb288104
--- /dev/null
+++ b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/TransferBatch.java
@@ -0,0 +1,61 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.fabric.samples.erc1155.models;
+
+import com.owlike.genson.annotation.JsonProperty;
+import org.hyperledger.fabric.contract.annotation.Property;
+
+public class TransferBatch extends Event {
+
+ @Property()
+ @JsonProperty("ids")
+ private long[] ids;
+
+ @Property()
+ @JsonProperty("values")
+ private long[] values;
+
+ /** Default constructor */
+ public TransferBatch() {
+ super();
+ }
+
+ /**
+ * Constructor of the class
+ *
+ * @param operator
+ * @param from
+ * @param to
+ * @param ids
+ * @param values
+ */
+ public TransferBatch(
+ final String operator,
+ final String from,
+ final String to,
+ final long[] ids,
+ final long[] values) {
+ super(operator, from, to);
+ this.ids = ids;
+ this.values = values;
+ }
+
+ /**
+ * Getter function for Ids
+ *
+ * @return all token Ids
+ */
+ public long[] getIds() {
+ return ids;
+ }
+
+ /**
+ * Getter function for the values
+ *
+ * @return get the amount or value of all the tokens
+ */
+ public long[] getValues() {
+ return values;
+ }
+}
diff --git a/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/TransferBatchMultiRecipient.java b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/TransferBatchMultiRecipient.java
new file mode 100755
index 00000000..337084bd
--- /dev/null
+++ b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/TransferBatchMultiRecipient.java
@@ -0,0 +1,103 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.fabric.samples.erc1155.models;
+
+import com.owlike.genson.annotation.JsonProperty;
+import org.hyperledger.fabric.contract.annotation.Property;
+
+public class TransferBatchMultiRecipient {
+
+ @Property()
+ @JsonProperty("operator")
+ private String operator;
+
+ @Property()
+ @JsonProperty("from")
+ private String from;
+
+ @Property()
+ @JsonProperty("to")
+ private String[] to;
+
+ @Property()
+ @JsonProperty("ids")
+ private long[] ids;
+
+ @Property()
+ @JsonProperty("values")
+ private long[] values;
+
+ /** Default constructor */
+ public TransferBatchMultiRecipient() {
+ super();
+ }
+
+ /**
+ * Constructor of the class
+ *
+ * @param operator
+ * @param from
+ * @param to
+ * @param ids
+ * @param values
+ */
+ public TransferBatchMultiRecipient(
+ final String operator,
+ final String from,
+ final String[] to,
+ final long[] ids,
+ final long[] values) {
+ super();
+ this.operator = operator;
+ this.from = from;
+ this.to = to;
+ this.ids = ids;
+ this.values = values;
+ }
+
+ /**
+ * getter function for from account
+ *
+ * @return get the from account
+ */
+ public String getFrom() {
+ return from;
+ }
+
+ /**
+ * getter function for Ids
+ *
+ * @return get all token ids
+ */
+ public long[] getIds() {
+ return ids;
+ }
+
+ /**
+ * getter function for the operator
+ *
+ * @return gett all aproved token operators
+ */
+ public String getOperator() {
+ return operator;
+ }
+
+ /**
+ * getter function for recipients
+ *
+ * @return all the recipients
+ */
+ public String[] getTo() {
+ return to;
+ }
+
+ /**
+ * getter function for all the values
+ *
+ * @return get all the token values or ammount
+ */
+ public long[] getValues() {
+ return values;
+ }
+}
diff --git a/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/TransferSingle.java b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/TransferSingle.java
new file mode 100755
index 00000000..60c1475c
--- /dev/null
+++ b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/TransferSingle.java
@@ -0,0 +1,57 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.fabric.samples.erc1155.models;
+
+import org.hyperledger.fabric.contract.annotation.Property;
+
+import com.owlike.genson.annotation.JsonProperty;
+
+public class TransferSingle extends Event {
+
+ @Property()
+ @JsonProperty("id")
+ private long id;
+
+ @Property()
+ @JsonProperty("value")
+ private long value;
+
+ /** Default constructor */
+ public TransferSingle() {
+ super();
+ }
+
+ /**
+ * Constructor of the class
+ *
+ * @param operator
+ * @param from
+ * @param to
+ * @param id
+ * @param value
+ */
+ public TransferSingle(final String operator, final String from, final String to, final long id, final long value) {
+ super(operator, from, to);
+ this.id = id;
+ this.value = value;
+ }
+
+ /**
+ * gettter function for id
+ *
+ * @return get the token id
+ */
+ public long getId() {
+ return id;
+ }
+
+ /**
+ * getter function for value
+ *
+ * @return the value or amount of the token id
+ */
+ public long getValue() {
+ return value;
+ }
+}
diff --git a/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/URI.java b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/URI.java
new file mode 100755
index 00000000..a8692f43
--- /dev/null
+++ b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/models/URI.java
@@ -0,0 +1,53 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.fabric.samples.erc1155.models;
+
+import com.owlike.genson.annotation.JsonProperty;
+import org.hyperledger.fabric.contract.annotation.Property;
+
+public class URI {
+
+ @Property()
+ @JsonProperty("id")
+ private long id;
+
+ @Property()
+ @JsonProperty("value")
+ private long value;
+
+ /** Default constructor */
+ public URI() {
+ super();
+ }
+
+ /**
+ * Constructor of the class
+ *
+ * @param id
+ * @param value
+ */
+ public URI(final long id, final long value) {
+ super();
+ this.id = id;
+ this.value = value;
+ }
+
+ /**
+ * getter function for the id
+ *
+ * @return token id
+ */
+ public long getId() {
+ return id;
+ }
+
+ /**
+ * getter function for the value
+ *
+ * @return
+ */
+ public long getValue() {
+ return value;
+ }
+}
diff --git a/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/utils/ContractUtility.java b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/utils/ContractUtility.java
new file mode 100755
index 00000000..0e50a0e0
--- /dev/null
+++ b/token-erc-1155/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc1155/utils/ContractUtility.java
@@ -0,0 +1,14 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.fabric.samples.erc1155.utils;
+
+public final class ContractUtility {
+
+ private ContractUtility() {
+ }
+
+ public static boolean stringIsNullOrEmpty(final String string) {
+ return string == null || string.isEmpty();
+ }
+}
diff --git a/token-erc-1155/chaincode-java/src/test/java/org/hyperledger/fabric/samples/erc1155/CommonUtils.java b/token-erc-1155/chaincode-java/src/test/java/org/hyperledger/fabric/samples/erc1155/CommonUtils.java
new file mode 100755
index 00000000..3bbb5999
--- /dev/null
+++ b/token-erc-1155/chaincode-java/src/test/java/org/hyperledger/fabric/samples/erc1155/CommonUtils.java
@@ -0,0 +1,151 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.hyperledger.fabric.samples.erc1155;
+
+import org.hyperledger.fabric.contract.ClientIdentity;
+import org.hyperledger.fabric.shim.ChaincodeStub;
+import org.hyperledger.fabric.shim.ledger.CompositeKey;
+import org.hyperledger.fabric.shim.ledger.KeyValue;
+import org.hyperledger.fabric.shim.ledger.QueryResultsIterator;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.hyperledger.fabric.samples.erc1155.Constants.TOKEN_NAME;
+import static org.hyperledger.fabric.samples.erc1155.Constants.TOKEN_SYMBOL;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class CommonUtils {
+
+ /** Default constructor */
+ public CommonUtils() {
+ super();
+ // TODO Auto-generated constructor stub
+ }
+
+ /**
+ * Set token name and symbol
+ *
+ * @param stub
+ */
+ protected void setTokenNameAndSysmbol(final ChaincodeStub stub) {
+ when(stub.getStringState(ContractConstants.NAME_KEY.getValue())).thenReturn(TOKEN_NAME);
+ when(stub.getStringState(ContractConstants.SYMBOL_KEY.getValue())).thenReturn(TOKEN_SYMBOL);
+ }
+
+ /**
+ * Set MSPID
+ *
+ * @param ci
+ */
+ protected void setOrg1MspId(final ClientIdentity ci) {
+ when(ci.getMSPID()).thenReturn("Org1MSP");
+ }
+
+ /**
+ * Set org2 msp id
+ *
+ * @param ci
+ */
+ protected void setOrg2MspId(final ClientIdentity ci) {
+ when(ci.getMSPID()).thenReturn("Org2MSP");
+ }
+
+ /**
+ * Create composite key
+ *
+ * @param stub
+ * @param returnValue
+ * @param firstKey
+ * @param keys
+ * @return
+ */
+ protected CompositeKey createCompositeKey(
+ final ChaincodeStub stub,
+ final String returnValue,
+ final ContractConstants firstKey,
+ final String... keys) {
+ CompositeKey ck = mock(CompositeKey.class);
+ StringBuilder keyCombined = new StringBuilder();
+ keyCombined.append(ContractConstants.BALANCE_PREFIX.getValue());
+ for (String key : keys) {
+ keyCombined.append(key);
+ }
+ when(stub.createCompositeKey(firstKey.getValue(), keys)).thenReturn(ck);
+ when(ck.toString()).thenReturn(keyCombined.toString());
+ when(stub.getStringState(ck.toString())).thenReturn(returnValue);
+ return ck;
+ }
+
+ /**
+ * Set iterator for the key
+ *
+ * @param stub
+ * @param firstKey
+ * @param keys
+ * @param values
+ */
+ protected void setResultsIterator(
+ final ChaincodeStub stub,
+ final ContractConstants firstKey,
+ final String[] keys,
+ final MockKeyValue... values) {
+ List list = new ArrayList();
+ for (MockKeyValue value : values) {
+ list.add(value);
+ }
+ when(stub.getStateByPartialCompositeKey(firstKey.getValue(), keys))
+ .thenReturn(new MockAssetResultsIterator(list));
+ }
+
+ final class MockKeyValue implements KeyValue {
+
+ private final String key;
+ private final String value;
+
+ MockKeyValue(final String key, final String value) {
+ super();
+ this.key = key;
+ this.value = value;
+ }
+
+ @Override
+ public String getKey() {
+ return this.key;
+ }
+
+ @Override
+ public String getStringValue() {
+ return this.value;
+ }
+
+ @Override
+ public byte[] getValue() {
+ return this.value.getBytes();
+ }
+ }
+
+ final class MockAssetResultsIterator implements QueryResultsIterator {
+
+ private final List assetList;
+
+ MockAssetResultsIterator(final List list) {
+ super();
+ this.assetList = list;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return assetList.iterator();
+ }
+
+ @Override
+ public void close() throws Exception {
+ // do nothing
+ }
+ }
+}
diff --git a/token-erc-1155/chaincode-java/src/test/java/org/hyperledger/fabric/samples/erc1155/Constants.java b/token-erc-1155/chaincode-java/src/test/java/org/hyperledger/fabric/samples/erc1155/Constants.java
new file mode 100755
index 00000000..73219da8
--- /dev/null
+++ b/token-erc-1155/chaincode-java/src/test/java/org/hyperledger/fabric/samples/erc1155/Constants.java
@@ -0,0 +1,25 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.hyperledger.fabric.samples.erc1155;
+
+public final class Constants {
+
+ /** Default constructor */
+ private Constants() {
+ super();
+ }
+
+ public static final String ORG1_USER_ID =
+ "x509::CN=User0@org1.example.com, L=San Francisco, ST=California,"
+ + " C=US::CN=ca.org2.example.com, O=org2.example.com, L=San Francisco, ST=California, C=US";
+ public static final String ORG2_USER_ID =
+ "x509::CN=User1@org2.example.com, L=San Francisco, ST=California,"
+ + " C=US::CN=ca.org2.example.com, O=org2.example.com, L=San Francisco, ST=California, C=US";
+ public static final String ORG3_USER_ID =
+ "x509::CN=User1@org3.example.com, L=San Francisco, ST=California,"
+ + " C=US::CN=ca.org3.example.com, O=org3.example.com, L=San Francisco, ST=California, C=US";
+ public static final String TOKEN_NAME = "AirlineToke";
+ public static final String TOKEN_SYMBOL = "ART";
+}
diff --git a/token-erc-1155/chaincode-java/src/test/java/org/hyperledger/fabric/samples/erc1155/ERC1155ContractTest.java b/token-erc-1155/chaincode-java/src/test/java/org/hyperledger/fabric/samples/erc1155/ERC1155ContractTest.java
new file mode 100755
index 00000000..067cdcc8
--- /dev/null
+++ b/token-erc-1155/chaincode-java/src/test/java/org/hyperledger/fabric/samples/erc1155/ERC1155ContractTest.java
@@ -0,0 +1,1320 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.hyperledger.fabric.samples.erc1155;
+
+import com.owlike.genson.Genson;
+import org.hyperledger.fabric.contract.ClientIdentity;
+import org.hyperledger.fabric.contract.Context;
+import org.hyperledger.fabric.samples.erc1155.models.ApprovalForAll;
+import org.hyperledger.fabric.samples.erc1155.models.TransferBatch;
+import org.hyperledger.fabric.samples.erc1155.models.TransferBatchMultiRecipient;
+import org.hyperledger.fabric.samples.erc1155.models.TransferSingle;
+import org.hyperledger.fabric.shim.ChaincodeException;
+import org.hyperledger.fabric.shim.ChaincodeStub;
+import org.hyperledger.fabric.shim.ledger.CompositeKey;
+import org.hyperledger.fabric.shim.ledger.KeyValue;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.ThrowableAssert.catchThrowable;
+import static org.hyperledger.fabric.samples.erc1155.Constants.ORG1_USER_ID;
+import static org.hyperledger.fabric.samples.erc1155.Constants.ORG2_USER_ID;
+import static org.hyperledger.fabric.samples.erc1155.Constants.ORG3_USER_ID;
+import static org.hyperledger.fabric.samples.erc1155.Constants.TOKEN_NAME;
+import static org.hyperledger.fabric.samples.erc1155.Constants.TOKEN_SYMBOL;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class ERC1155ContractTest extends CommonUtils {
+
+ @Nested
+ class ContractInitalizeFunctionsTest {
+
+ private ERC1155Contract contract;
+ private Context ctx;
+ private ChaincodeStub stub;
+ private ClientIdentity ci = null;
+
+ @BeforeEach
+ public void initialize() {
+ this.contract = new ERC1155Contract();
+ this.ctx = mock(Context.class);
+ ci = mock(ClientIdentity.class);
+ this.stub = mock(ChaincodeStub.class);
+ when(this.ctx.getStub()).thenReturn(this.stub);
+ when(this.ctx.getClientIdentity()).thenReturn(ci);
+ }
+
+ @Test
+ public void invokeInitializeTest() {
+ setOrg1MspId(this.ci);
+ this.contract.Initialize(this.ctx, TOKEN_NAME, TOKEN_SYMBOL);
+ verify(this.stub).putStringState(ContractConstants.NAME_KEY.getValue(), TOKEN_NAME);
+ verify(this.stub).putStringState(ContractConstants.SYMBOL_KEY.getValue(), TOKEN_SYMBOL);
+ }
+
+ @Test
+ public void unAuthorizedOrgInitializeTest() {
+ setOrg2MspId(this.ci);
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.Initialize(ctx, TOKEN_NAME, TOKEN_SYMBOL);
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage("Client is not authorized to initialize the contract");
+ }
+
+ @Test
+ public void queryNameTest() {
+ setOrg1MspId(this.ci);
+ setTokenNameAndSysmbol(stub);
+ String name = this.contract.Name(ctx);
+ assertThat(name).isEqualTo(TOKEN_NAME);
+ }
+
+ @Test
+ public void querySymbolTest() {
+ setOrg1MspId(this.ci);
+ setTokenNameAndSysmbol(stub);
+ String name = this.contract.Symbol(this.ctx);
+ assertThat(name).isEqualTo(TOKEN_SYMBOL);
+ }
+ }
+
+ @Nested
+ class ContractMintFunctionsTest {
+
+ private ERC1155Contract contract;
+ private Context ctx;
+ private ChaincodeStub stub;
+ private ClientIdentity ci = null;
+
+ @BeforeEach
+ public void initialize() {
+ this.contract = new ERC1155Contract();
+ this.ctx = mock(Context.class);
+ this.stub = mock(ChaincodeStub.class);
+ this.ci = mock(ClientIdentity.class);
+ when(this.ctx.getStub()).thenReturn(this.stub);
+ when(this.ctx.getClientIdentity()).thenReturn(this.ci);
+ }
+
+ @Test
+ public void invokeMintWithoutInitializeTest() {
+ setOrg1MspId(this.ci);
+ when(this.ci.getId()).thenReturn(ORG1_USER_ID);
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.Mint(this.ctx, ORG1_USER_ID, 1, 1);
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage(
+ "Contract options need to be set before calling any function, call Initialize() to initialize contract");
+ }
+
+ @Test
+ public void unAuthorizedInvokeMintTest() {
+ setOrg2MspId(this.ci);
+ when(ci.getId()).thenReturn(ORG2_USER_ID);
+ setTokenNameAndSysmbol(stub);
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.Mint(this.ctx, ORG2_USER_ID, 1, 1);
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage("Client is not authorized to set the name and symbol of the token");
+ }
+
+ @Test
+ public void invalidAmountInvokeMintTest() {
+ setOrg1MspId(ci);
+ when(this.ci.getId()).thenReturn(ORG1_USER_ID);
+ setTokenNameAndSysmbol(stub);
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.Mint(this.ctx, ORG2_USER_ID, 1, 0);
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage("Mint amount must be a positive integer");
+ }
+
+ @Test
+ public void invalidAccountInvokeMintTest() {
+ setOrg1MspId(this.ci);
+ when(this.ci.getId()).thenReturn(ORG1_USER_ID);
+ setTokenNameAndSysmbol(stub);
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.Mint(ctx, "0x0", 1, 1);
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage("mint to the zero address");
+ }
+
+ @Test
+ public void invokeMintTest() {
+
+ setOrg1MspId(ci);
+ when(this.ci.getId()).thenReturn(ORG1_USER_ID);
+ setTokenNameAndSysmbol(stub);
+ CompositeKey ck =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG1_USER_ID, "1", ORG1_USER_ID);
+ this.contract.Mint(this.ctx, ORG1_USER_ID, 1, 1);
+ verify(this.stub).putStringState(ck.toString(), "1");
+ TransferSingle transferSingleEvent =
+ new TransferSingle(ORG1_USER_ID, "0x0", ORG1_USER_ID, 1, 1);
+ verify(this.stub)
+ .setEvent("TransferSingle", new Genson().serialize(transferSingleEvent).getBytes(UTF_8));
+ }
+
+ @Test
+ public void invokeBatchMintWithoutInitializeTest() {
+ setOrg1MspId(this.ci);
+ when(ci.getId()).thenReturn(ORG1_USER_ID);
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.MintBatch(this.ctx, ORG1_USER_ID, new long[] {1}, new long[] {1});
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage(
+ "Contract options need to be set before calling any function, call Initialize() to initialize contract");
+ }
+
+ @Test
+ public void misMatchedTokenIdsAndAmountsInputBatchMintTest() {
+ setOrg1MspId(this.ci);
+ when(ci.getId()).thenReturn(ORG1_USER_ID);
+ setTokenNameAndSysmbol(stub);
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.MintBatch(ctx, ORG1_USER_ID, new long[] {1, 2}, new long[] {1});
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage("ids and amounts must have the same length");
+ }
+
+ @Test
+ public void unAuthorizedBatchMintTest() {
+ setOrg2MspId(this.ci);
+ when(ci.getId()).thenReturn(ORG2_USER_ID);
+ setTokenNameAndSysmbol(stub);
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.MintBatch(ctx, ORG2_USER_ID, new long[] {1}, new long[] {1});
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage("Client is not authorized to set the name and symbol of the token");
+ }
+
+ @Test
+ public void invalidAmountInvokeMintBatchTest() {
+ setOrg1MspId(this.ci);
+ when(ci.getId()).thenReturn(ORG1_USER_ID);
+ setTokenNameAndSysmbol(stub);
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.MintBatch(this.ctx, ORG2_USER_ID, new long[] {1}, new long[] {0});
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage("Mint amount must be a positive integer");
+ }
+
+ @Test
+ public void invalidAccountInvokeMintBatchTest() {
+ setOrg1MspId(this.ci);
+ when(ci.getId()).thenReturn(ORG1_USER_ID);
+ setTokenNameAndSysmbol(stub);
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.MintBatch(this.ctx, "0x0", new long[] {1}, new long[] {0});
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage("mint to the zero address");
+ }
+
+ @Test
+ public void invokeBatchMintTest() {
+ setOrg1MspId(this.ci);
+ when(ci.getId()).thenReturn(ORG1_USER_ID);
+ CompositeKey ck1 =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG1_USER_ID, "1", ORG1_USER_ID);
+ CompositeKey ck2 =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG1_USER_ID, "2", ORG1_USER_ID);
+ setTokenNameAndSysmbol(stub);
+ this.contract.MintBatch(ctx, ORG1_USER_ID, new long[] {1, 2}, new long[] {1, 1});
+ verify(this.stub).putStringState(ck1.toString(), "1");
+ verify(this.stub).putStringState(ck2.toString(), "1");
+ TransferBatch transferBatchEvent =
+ new TransferBatch(
+ ORG1_USER_ID, "0x0", ORG1_USER_ID, new long[] {1, 2}, new long[] {1, 1});
+ verify(this.stub)
+ .setEvent("TransferBatch", new Genson().serialize(transferBatchEvent).getBytes(UTF_8));
+ verify(this.stub)
+ .putStringState(
+ ContractConstants.BALANCE_PREFIX.getValue() + ORG1_USER_ID + "2" + ORG1_USER_ID, "1");
+ }
+ }
+
+ @Nested
+ class ContractBurnFunctionsTest {
+
+ private ERC1155Contract contract;
+ private Context ctx;
+ private ChaincodeStub stub;
+ private CompositeKey ck1;
+ private ClientIdentity ci = null;
+
+ @BeforeEach
+ public void initialize() {
+ this.contract = new ERC1155Contract();
+ this.ctx = mock(Context.class);
+ this.stub = mock(ChaincodeStub.class);
+ when(this.ctx.getStub()).thenReturn(this.stub);
+ ci = mock(ClientIdentity.class);
+ ck1 = mock(CompositeKey.class);
+ when(this.ctx.getClientIdentity()).thenReturn(ci);
+ when(ci.getId()).thenReturn(ORG1_USER_ID);
+ when(ck1.toString())
+ .thenReturn(
+ ContractConstants.BALANCE_PREFIX.getValue() + ORG1_USER_ID + "1" + ORG1_USER_ID);
+ when(this.stub.createCompositeKey(
+ ContractConstants.BALANCE_PREFIX.getValue(), ORG1_USER_ID, "1", ORG1_USER_ID))
+ .thenReturn(ck1);
+ when(stub.getStringState(ck1.toString())).thenReturn("");
+ }
+
+ @Test
+ public void invokeTokenBurnTest() {
+
+ setOrg1MspId(this.ci);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG1_USER_ID, "2", ORG1_USER_ID);
+ setTokenNameAndSysmbol(stub);
+ List list = new ArrayList();
+ list.add(new MockKeyValue("1", "10"));
+ list.add(new MockKeyValue("2", "10"));
+ when(stub.getStateByPartialCompositeKey(
+ ContractConstants.BALANCE_PREFIX.getValue(), ORG1_USER_ID, "1"))
+ .thenReturn(new MockAssetResultsIterator(list));
+ when(stub.splitCompositeKey("1")).thenReturn(ck1);
+ this.contract.Burn(ctx, ORG1_USER_ID, 1, 1);
+ verify(this.stub).putStringState(ck1.toString(), "9");
+ }
+
+ @Test
+ public void invokeBurnWithoutInitializeTest() {
+ setOrg1MspId(this.ci);
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.Burn(ctx, ORG1_USER_ID, 1, 1);
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage(
+ "Contract options need to be set before calling any function, call Initialize() to initialize contract");
+ }
+
+ @Test
+ public void invokeWithInSufficientFundTokenBurnTest() {
+
+ setOrg1MspId(this.ci);
+ setTokenNameAndSysmbol(stub);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG1_USER_ID, "2", ORG1_USER_ID);
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"));
+ when(stub.splitCompositeKey("1")).thenReturn(ck1);
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.Burn(ctx, ORG1_USER_ID, 1, 12);
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage(
+ String.format(
+ "sender has insufficient funds for token %s, needed funds: %d, available fund: %d",
+ "1", 12, 10));
+ }
+
+ @Test
+ public void invokeWithZeroAddressTokenBurnTest() {
+
+ setOrg1MspId(this.ci);
+ setTokenNameAndSysmbol(stub);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG1_USER_ID, "2", ORG1_USER_ID);
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"));
+ when(stub.splitCompositeKey("1")).thenReturn(ck1);
+
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.Burn(ctx, "0x0", 1, 12);
+ });
+
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage("burn to the zero address");
+ }
+
+ @Test
+ public void invokeTokenBatchBurnTest() {
+
+ setTokenNameAndSysmbol(stub);
+ setOrg1MspId(this.ci);
+ CompositeKey ck2 =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG1_USER_ID, "2", ORG1_USER_ID);
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"));
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "2"},
+ new MockKeyValue("2", "10"));
+ when(stub.splitCompositeKey("1")).thenReturn(ck1);
+ when(stub.splitCompositeKey("2")).thenReturn(ck2);
+ this.contract.BurnBatch(ctx, ORG1_USER_ID, new long[] {1, 2}, new long[] {1, 1});
+ verify(this.stub).putStringState(ck1.toString(), "9");
+ verify(this.stub).putStringState(ck2.toString(), "9");
+ }
+
+ @Test
+ public void invokeBurnBatchWithoutInitializeTest() {
+ setOrg1MspId(this.ci);
+ when(this.ctx.getClientIdentity()).thenReturn(ci);
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.BurnBatch(ctx, ORG1_USER_ID, new long[] {1, 2}, new long[] {1, 1});
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage(
+ "Contract options need to be set before calling any function, call Initialize() to initialize contract");
+ }
+
+ @Test
+ public void invokeInsufficientFundTokenBatchBurnTest() {
+ setTokenNameAndSysmbol(this.stub);
+ setOrg1MspId(this.ci);
+ CompositeKey ck2 =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG1_USER_ID, "2", ORG1_USER_ID);
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"));
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "2"},
+ new MockKeyValue("2", "10"));
+ when(stub.splitCompositeKey("1")).thenReturn(ck1);
+ when(stub.splitCompositeKey("2")).thenReturn(ck2);
+
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.BurnBatch(ctx, ORG1_USER_ID, new long[] {1, 2}, new long[] {12, 12});
+ });
+
+ String error =
+ String.format(
+ "sender has insufficient funds for token %s, needed funds: %d, available fund: %d",
+ "1", 12, 10);
+ assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause().hasMessage(error);
+ }
+ }
+
+ @Nested
+ class ContractTransferFunctionsTest {
+
+ private ERC1155Contract contract;
+ private Context ctx;
+ private ChaincodeStub stub;
+ private CompositeKey ck1;
+ private ClientIdentity ci = null;
+
+ @BeforeEach
+ public void initialize() {
+ this.contract = new ERC1155Contract();
+ this.ctx = mock(Context.class);
+ this.stub = mock(ChaincodeStub.class);
+ when(this.ctx.getStub()).thenReturn(this.stub);
+ ck1 =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG1_USER_ID, "1", ORG1_USER_ID);
+ ci = mock(ClientIdentity.class);
+ when(ci.getMSPID()).thenReturn("Org1MSP");
+ when(ci.getId()).thenReturn(ORG1_USER_ID);
+ when(this.ctx.getClientIdentity()).thenReturn(ci);
+ when(stub.splitCompositeKey("1")).thenReturn(ck1);
+ }
+
+ @Test
+ public void invokeTokenTransferTest() {
+ CompositeKey org2Balance =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "1", ORG1_USER_ID);
+ setTokenNameAndSysmbol(this.stub);
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"));
+ this.contract.TransferFrom(ctx, ORG1_USER_ID, ORG2_USER_ID, 1, 2);
+ verify(this.stub).putStringState(ck1.toString(), "8");
+ verify(this.stub).putStringState(org2Balance.toString(), "2");
+ TransferSingle transferSingleEvent =
+ new TransferSingle(ORG1_USER_ID, ORG1_USER_ID, ORG2_USER_ID, 1, 2);
+ verify(this.stub)
+ .setEvent("TransferSingle", new Genson().serialize(transferSingleEvent).getBytes(UTF_8));
+ }
+
+ @Test
+ public void invokeTokenTransferByOperatorTest() {
+ when(this.ci.getMSPID()).thenReturn("Org3MSP");
+ when(this.ci.getId()).thenReturn(ORG3_USER_ID);
+ CompositeKey org2Balance =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "1", ORG1_USER_ID);
+ createCompositeKey(
+ stub, "true", ContractConstants.APPROVAL_PREFIX, ORG1_USER_ID, ORG3_USER_ID);
+ setTokenNameAndSysmbol(this.stub);
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"));
+ this.contract.TransferFrom(ctx, ORG1_USER_ID, ORG2_USER_ID, 1, 2);
+ verify(this.stub).putStringState(ck1.toString(), "8");
+ verify(this.stub).putStringState(org2Balance.toString(), "2");
+ TransferSingle transferSingleEvent =
+ new TransferSingle(ORG3_USER_ID, ORG1_USER_ID, ORG2_USER_ID, 1, 2);
+ verify(this.stub)
+ .setEvent("TransferSingle", new Genson().serialize(transferSingleEvent).getBytes(UTF_8));
+ }
+
+ @Test
+ public void invokeTokenTransferWithUnApprovedOperatorTest() {
+
+ setTokenNameAndSysmbol(this.stub);
+ when(ci.getMSPID()).thenReturn("Org3MSP");
+ when(ci.getId()).thenReturn(ORG3_USER_ID);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "1", ORG1_USER_ID);
+ createCompositeKey(stub, "", ContractConstants.APPROVAL_PREFIX, ORG1_USER_ID, ORG3_USER_ID);
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"));
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.TransferFrom(ctx, ORG1_USER_ID, ORG2_USER_ID, 1, 2);
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage("caller is not owner nor is approved");
+ }
+
+ @Test
+ public void invokeTokenTransferWithZeroAddressReceiverTest() {
+ setTokenNameAndSysmbol(this.stub);
+ when(ci.getMSPID()).thenReturn("Org3MSP");
+ when(ci.getId()).thenReturn(ORG3_USER_ID);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "1", ORG1_USER_ID);
+ createCompositeKey(stub, "", ContractConstants.APPROVAL_PREFIX, ORG1_USER_ID, ORG3_USER_ID);
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"));
+
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.TransferFrom(ctx, ORG1_USER_ID, "0x0", 1, 2);
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage("transfer to the zero address");
+ }
+
+ @Test
+ public void invokeTokenTransferWithInsufficientBalanceTest() {
+
+ setTokenNameAndSysmbol(this.stub);
+ when(ci.getMSPID()).thenReturn("Org3MSP");
+ when(ci.getId()).thenReturn(ORG3_USER_ID);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "1", ORG1_USER_ID);
+ createCompositeKey(
+ stub, "true", ContractConstants.APPROVAL_PREFIX, ORG1_USER_ID, ORG3_USER_ID);
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"));
+
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.TransferFrom(ctx, ORG1_USER_ID, ORG2_USER_ID, 1, 12);
+ });
+ String error =
+ String.format(
+ "sender has insufficient funds for token %s, needed funds: %d, available fund: %d",
+ "1", 12, 10);
+ assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause().hasMessage(error);
+ }
+
+ @Test
+ public void invokeTokenTransferBySelfTest() {
+ setTokenNameAndSysmbol(this.stub);
+
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "1", ORG1_USER_ID);
+ createCompositeKey(
+ stub, "true", ContractConstants.APPROVAL_PREFIX, ORG1_USER_ID, ORG3_USER_ID);
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"));
+
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.TransferFrom(ctx, ORG1_USER_ID, ORG1_USER_ID, 1, 12);
+ });
+ String error = "transfer to self";
+ assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause().hasMessage(error);
+ }
+
+ @Test
+ public void invokeTokenBatchTransferTest() {
+ setTokenNameAndSysmbol(this.stub);
+
+ CompositeKey org1BalancePrefix =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG1_USER_ID, "2", ORG1_USER_ID);
+
+ CompositeKey org2Token1BalancePrefix =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "1", ORG1_USER_ID);
+
+ CompositeKey org2Token2BalancePrefix =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "2", ORG1_USER_ID);
+
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"),
+ new MockKeyValue("1", "10"));
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "2"},
+ new MockKeyValue("2", "10"),
+ new MockKeyValue("2", "10"));
+
+ when(stub.splitCompositeKey("1")).thenReturn(org1BalancePrefix);
+ when(stub.splitCompositeKey("2")).thenReturn(org2Token1BalancePrefix);
+ this.contract.BatchTransferFrom(
+ ctx, ORG1_USER_ID, ORG2_USER_ID, new long[] {1, 2}, new long[] {1, 1});
+ verify(this.stub).putStringState(ck1.toString(), "9");
+ verify(this.stub).putStringState(org2Token1BalancePrefix.toString(), "1");
+ verify(this.stub).putStringState(ck1.toString(), "9");
+ verify(this.stub).putStringState(org2Token2BalancePrefix.toString(), "1");
+
+ TransferBatch transferBatchEvent =
+ new TransferBatch(
+ ORG1_USER_ID, ORG1_USER_ID, ORG2_USER_ID, new long[] {1, 2}, new long[] {1, 1});
+ verify(this.stub)
+ .setEvent("TransferBatch", new Genson().serialize(transferBatchEvent).getBytes(UTF_8));
+ }
+
+ @Test
+ public void invokeTokenBatchTransferByOperatorTest() {
+
+ setTokenNameAndSysmbol(this.stub);
+ when(ci.getMSPID()).thenReturn("Org3MSP");
+ when(ci.getId()).thenReturn(ORG3_USER_ID);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG1_USER_ID, "2", ORG1_USER_ID);
+ CompositeKey org2Token1BalancePrefix =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "1", ORG1_USER_ID);
+ CompositeKey org2Token2BalancePrefix =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "2", ORG1_USER_ID);
+ createCompositeKey(
+ stub, "true", ContractConstants.APPROVAL_PREFIX, ORG1_USER_ID, ORG3_USER_ID);
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"),
+ new MockKeyValue("1", "10"));
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "2"},
+ new MockKeyValue("2", "10"),
+ new MockKeyValue("2", "10"));
+ when(stub.splitCompositeKey("1")).thenReturn(ck1);
+ when(stub.splitCompositeKey("2")).thenReturn(org2Token1BalancePrefix);
+ this.contract.BatchTransferFrom(
+ ctx, ORG1_USER_ID, ORG2_USER_ID, new long[] {1, 2}, new long[] {1, 1});
+ verify(this.stub).putStringState(ck1.toString(), "9");
+ verify(this.stub).putStringState(org2Token1BalancePrefix.toString(), "1");
+ verify(this.stub).putStringState(ck1.toString(), "9");
+ verify(this.stub).putStringState(org2Token2BalancePrefix.toString(), "1");
+ TransferBatch transferBatchEvent =
+ new TransferBatch(
+ ORG3_USER_ID, ORG1_USER_ID, ORG2_USER_ID, new long[] {1, 2}, new long[] {1, 1});
+ verify(this.stub)
+ .setEvent("TransferBatch", new Genson().serialize(transferBatchEvent).getBytes(UTF_8));
+ }
+
+ @Test
+ public void invokeTokenBatchTransferByUnApprovedOperatorTest() {
+ setTokenNameAndSysmbol(this.stub);
+ when(ci.getMSPID()).thenReturn("Org3MSP");
+ when(ci.getId()).thenReturn(ORG3_USER_ID);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "1", ORG1_USER_ID);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "2", ORG1_USER_ID);
+ createCompositeKey(stub, "", ContractConstants.APPROVAL_PREFIX, ORG1_USER_ID, ORG3_USER_ID);
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"),
+ new MockKeyValue("1", "10"));
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "2"},
+ new MockKeyValue("2", "10"),
+ new MockKeyValue("2", "10"));
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.BatchTransferFrom(
+ ctx, ORG1_USER_ID, ORG2_USER_ID, new long[] {1, 2}, new long[] {1, 1});
+ });
+ String error = "caller is not owner nor is approved";
+ assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause().hasMessage(error);
+ }
+
+ @Test
+ public void invokeTokenBatchTransferWithMismatchedIdsAndAmountInputTest() {
+ setTokenNameAndSysmbol(this.stub);
+ when(ci.getMSPID()).thenReturn("Org3MSP");
+ when(ci.getId()).thenReturn(ORG3_USER_ID);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "1", ORG1_USER_ID);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "2", ORG1_USER_ID);
+ createCompositeKey(stub, "", ContractConstants.APPROVAL_PREFIX, ORG1_USER_ID, ORG3_USER_ID);
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"),
+ new MockKeyValue("1", "10"));
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "2"},
+ new MockKeyValue("2", "10"),
+ new MockKeyValue("2", "10"));
+ when(stub.splitCompositeKey("1")).thenReturn(ck1);
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.BatchTransferFrom(
+ ctx, ORG1_USER_ID, ORG2_USER_ID, new long[] {1}, new long[] {1, 1});
+ });
+ String error = "ids and amounts must have the same length";
+ assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause().hasMessage(error);
+ }
+
+ @Test
+ public void invokeTokenBatchTransferMultiReceipentByOperatorTest() {
+
+ setTokenNameAndSysmbol(this.stub);
+ when(ci.getMSPID()).thenReturn("Org3MSP");
+ when(ci.getId()).thenReturn(ORG3_USER_ID);
+ CompositeKey ck2 =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG1_USER_ID, "2", ORG1_USER_ID);
+ CompositeKey ck3 =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "1", ORG1_USER_ID);
+ createCompositeKey(
+ stub, "true", ContractConstants.APPROVAL_PREFIX, ORG1_USER_ID, ORG3_USER_ID);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "2", ORG1_USER_ID);
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"),
+ new MockKeyValue("1", "10"));
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "2"},
+ new MockKeyValue("2", "10"),
+ new MockKeyValue("2", "10"));
+ when(stub.splitCompositeKey("1")).thenReturn(ck1);
+ when(stub.splitCompositeKey("2")).thenReturn(ck2);
+ this.contract.BatchTransferFromMultiRecipient(
+ ctx,
+ ORG1_USER_ID,
+ new String[] {ORG2_USER_ID, ORG2_USER_ID},
+ new long[] {1, 2},
+ new long[] {1, 1});
+ verify(this.stub).putStringState(ck1.toString(), "9");
+ verify(this.stub).putStringState(ck3.toString(), "1");
+
+ verify(this.stub).putStringState(ck1.toString(), "9");
+ verify(this.stub).putStringState(ck3.toString(), "1");
+ TransferBatchMultiRecipient transferBatchMultiRecipientEvent =
+ new TransferBatchMultiRecipient(
+ ORG3_USER_ID,
+ ORG1_USER_ID,
+ new String[] {ORG2_USER_ID, ORG2_USER_ID},
+ new long[] {1, 2},
+ new long[] {1, 1});
+
+ verify(this.stub)
+ .setEvent(
+ "TransferBatchMultiRecipient",
+ new Genson().serialize(transferBatchMultiRecipientEvent).getBytes(UTF_8));
+ }
+
+ @Test
+ public void invokeTokenBatchTransferMultiReceipentTest() {
+
+ setTokenNameAndSysmbol(this.stub);
+ CompositeKey ck2 =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG1_USER_ID, "2", ORG1_USER_ID);
+ CompositeKey ck3 =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "1", ORG1_USER_ID);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG3_USER_ID, "2", ORG1_USER_ID);
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"),
+ new MockKeyValue("1", "10"));
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "2"},
+ new MockKeyValue("2", "10"),
+ new MockKeyValue("2", "10"));
+ when(stub.splitCompositeKey("1")).thenReturn(ck1);
+ when(stub.splitCompositeKey("2")).thenReturn(ck2);
+ this.contract.BatchTransferFromMultiRecipient(
+ ctx,
+ ORG1_USER_ID,
+ new String[] {ORG2_USER_ID, ORG3_USER_ID},
+ new long[] {1, 2},
+ new long[] {1, 1});
+ verify(this.stub).putStringState(ck1.toString(), "9");
+ verify(this.stub).putStringState(ck3.toString(), "1");
+ verify(this.stub).putStringState(ck1.toString(), "9");
+ verify(this.stub).putStringState(ck3.toString(), "1");
+ TransferBatchMultiRecipient transferBatchMultiRecipientEvent =
+ new TransferBatchMultiRecipient(
+ ORG1_USER_ID,
+ ORG1_USER_ID,
+ new String[] {ORG2_USER_ID, ORG3_USER_ID},
+ new long[] {1, 2},
+ new long[] {1, 1});
+ verify(this.stub)
+ .setEvent(
+ "TransferBatchMultiRecipient",
+ new Genson().serialize(transferBatchMultiRecipientEvent).getBytes(UTF_8));
+ }
+
+ @Test
+ public void invokeTokenBatchTransferMultiReceipentByUnAutorizedOperatorTest() {
+
+ setTokenNameAndSysmbol(this.stub);
+ when(ci.getMSPID()).thenReturn("Org3MSP");
+ when(ci.getId()).thenReturn(ORG3_USER_ID);
+ CompositeKey ck2 =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG1_USER_ID, "2", ORG1_USER_ID);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "1", ORG1_USER_ID);
+ createCompositeKey(
+ stub, "false", ContractConstants.APPROVAL_PREFIX, ORG1_USER_ID, ORG3_USER_ID);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "2", ORG1_USER_ID);
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"),
+ new MockKeyValue("1", "10"));
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "2"},
+ new MockKeyValue("2", "10"),
+ new MockKeyValue("2", "10"));
+ when(stub.splitCompositeKey("1")).thenReturn(ck1);
+ when(stub.splitCompositeKey("2")).thenReturn(ck2);
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.BatchTransferFromMultiRecipient(
+ ctx,
+ ORG1_USER_ID,
+ new String[] {ORG2_USER_ID, ORG2_USER_ID},
+ new long[] {1, 2},
+ new long[] {1, 1});
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage("caller is not owner nor is approved");
+ }
+
+ @Test
+ public void invokeTokenBatchTransferMultiReceipentWithMisMatchedRecipientsAndIdsTest() {
+
+ setTokenNameAndSysmbol(this.stub);
+ when(ci.getMSPID()).thenReturn("Org3MSP");
+ when(ci.getId()).thenReturn(ORG3_USER_ID);
+ CompositeKey ck2 =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG1_USER_ID, "2", ORG1_USER_ID);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "1", ORG1_USER_ID);
+ createCompositeKey(
+ stub, "false", ContractConstants.APPROVAL_PREFIX, ORG1_USER_ID, ORG3_USER_ID);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "2", ORG1_USER_ID);
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"),
+ new MockKeyValue("1", "10"));
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "2"},
+ new MockKeyValue("2", "10"),
+ new MockKeyValue("2", "10"));
+ when(stub.splitCompositeKey("1")).thenReturn(ck1);
+ when(stub.splitCompositeKey("2")).thenReturn(ck2);
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.BatchTransferFromMultiRecipient(
+ ctx,
+ ORG1_USER_ID,
+ new String[] {ORG2_USER_ID},
+ new long[] {1, 2},
+ new long[] {1, 1});
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage("recipients, ids, and amounts must have the same length");
+ }
+
+ @Test
+ public void invokeTokenBatchTransferMultiReceipentWithMisMatchedAmountsAndIdsTest() {
+
+ setTokenNameAndSysmbol(this.stub);
+ when(ci.getMSPID()).thenReturn("Org3MSP");
+ when(ci.getId()).thenReturn(ORG3_USER_ID);
+ CompositeKey ck2 =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG1_USER_ID, "2", ORG1_USER_ID);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "1", ORG1_USER_ID);
+ createCompositeKey(
+ stub, "false", ContractConstants.APPROVAL_PREFIX, ORG1_USER_ID, ORG3_USER_ID);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "2", ORG1_USER_ID);
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"),
+ new MockKeyValue("1", "10"));
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "2"},
+ new MockKeyValue("2", "10"),
+ new MockKeyValue("2", "10"));
+ when(stub.splitCompositeKey("1")).thenReturn(ck1);
+ when(stub.splitCompositeKey("2")).thenReturn(ck2);
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.BatchTransferFromMultiRecipient(
+ ctx,
+ ORG1_USER_ID,
+ new String[] {ORG2_USER_ID},
+ new long[] {1, 2},
+ new long[] {1, 1});
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage("recipients, ids, and amounts must have the same length");
+ }
+
+ @Test
+ public void invokeTokenBatchTransferMultiReceipentWithInsufficientFundTest() {
+
+ setTokenNameAndSysmbol(this.stub);
+ CompositeKey ck2 =
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG1_USER_ID, "2", ORG1_USER_ID);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "1", ORG1_USER_ID);
+ createCompositeKey(
+ stub, "", ContractConstants.BALANCE_PREFIX, ORG3_USER_ID, "2", ORG1_USER_ID);
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"),
+ new MockKeyValue("1", "10"));
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "2"},
+ new MockKeyValue("2", "10"),
+ new MockKeyValue("2", "10"));
+
+ when(stub.splitCompositeKey("1")).thenReturn(ck1);
+ when(stub.splitCompositeKey("2")).thenReturn(ck2);
+
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.BatchTransferFromMultiRecipient(
+ ctx,
+ ORG1_USER_ID,
+ new String[] {ORG2_USER_ID, ORG3_USER_ID},
+ new long[] {1, 2},
+ new long[] {31, 9});
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage(
+ "sender has insufficient funds for token 1, needed funds: 31, available fund: 20");
+ }
+ }
+
+ @Nested
+ class ContractUriFunctionsTest {
+
+ private ERC1155Contract contract;
+ private Context ctx;
+ private ChaincodeStub stub;
+ private ClientIdentity ci = null;
+
+ @BeforeEach
+ public void initialize() {
+ this.contract = new ERC1155Contract();
+ this.ctx = mock(Context.class);
+ ci = mock(ClientIdentity.class);
+ this.stub = mock(ChaincodeStub.class);
+ when(this.ctx.getStub()).thenReturn(this.stub);
+ when(this.ctx.getClientIdentity()).thenReturn(ci);
+ }
+
+ @Test
+ public void setUriTest() {
+ setTokenNameAndSysmbol(this.stub);
+ setOrg1MspId(this.ci);
+ when(ci.getId()).thenReturn(ORG1_USER_ID);
+ this.contract.SetURI(ctx, "http://ree/{id}.json");
+ verify(this.stub)
+ .putStringState(ContractConstants.URI_KEY.getValue(), "http://ree/{id}.json");
+ }
+
+ @Test
+ public void setUriWithoutIdTest() {
+ setTokenNameAndSysmbol(this.stub);
+ setOrg1MspId(this.ci);
+ when(ci.getId()).thenReturn(ORG1_USER_ID);
+ this.contract.SetURI(ctx, "http://ree/{id}.json");
+ verify(this.stub)
+ .putStringState(ContractConstants.URI_KEY.getValue(), "http://ree/{id}.json");
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.SetURI(ctx, "http://ree/2.json");
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage("failed to set uri, uri should contain '{id}'");
+ }
+
+ @Test
+ public void invokeGetApproveAllWithoutInitializeTest() {
+ setOrg1MspId(this.ci);
+ when(ci.getId()).thenReturn(ORG1_USER_ID);
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.SetURI(ctx, "http://ree/2.json");
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage(
+ "Contract options need to be set before calling any function, call Initialize() to initialize contract");
+ }
+
+ @Test
+ public void getUriTest() {
+ setTokenNameAndSysmbol(this.stub);
+ setOrg1MspId(this.ci);
+ when(ci.getId()).thenReturn(ORG1_USER_ID);
+ when(stub.getStringState(ContractConstants.URI_KEY.getValue()))
+ .thenReturn("http://ree/{id}.json");
+ String value = this.contract.URI(ctx, 1);
+ assertThat(value).isEqualTo("http://ree/{id}.json");
+ }
+ }
+
+ @Nested
+ class ContractApproveAndBalanceFunctionsTest {
+
+ private ERC1155Contract contract;
+ private Context ctx;
+ private ChaincodeStub stub;
+ private ClientIdentity ci = null;
+
+ @BeforeEach
+ public void initialize() {
+ this.contract = new ERC1155Contract();
+ this.ctx = mock(Context.class);
+ ci = mock(ClientIdentity.class);
+ this.stub = mock(ChaincodeStub.class);
+ when(this.ctx.getStub()).thenReturn(this.stub);
+ when(this.ctx.getClientIdentity()).thenReturn(ci);
+ }
+
+ @Test
+ public void setApproveForAllTest() {
+ setTokenNameAndSysmbol(this.stub);
+ setOrg1MspId(this.ci);
+ when(ci.getId()).thenReturn(ORG1_USER_ID);
+ CompositeKey ck =
+ createCompositeKey(
+ stub, "", ContractConstants.APPROVAL_PREFIX, ORG1_USER_ID, ORG2_USER_ID);
+ this.contract.SetApprovalForAll(ctx, ORG2_USER_ID, true);
+ verify(this.stub).putStringState(ck.toString(), "true");
+ ApprovalForAll approvalForAllEvent = new ApprovalForAll(ORG1_USER_ID, ORG2_USER_ID, true);
+ verify(this.stub)
+ .setEvent("ApprovalForAll", new Genson().serialize(approvalForAllEvent).getBytes(UTF_8));
+ }
+
+ @Test
+ public void invokeSetApproveAllWithoutInitializeTest() {
+ setOrg1MspId(this.ci);
+ when(this.ci.getId()).thenReturn(ORG1_USER_ID);
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.SetApprovalForAll(ctx, ORG2_USER_ID, true);
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage(
+ "Contract options need to be set before calling any function, call Initialize() to initialize contract");
+ }
+
+ @Test
+ public void isApproveForAllTest() {
+ setTokenNameAndSysmbol(this.stub);
+ setOrg1MspId(this.ci);
+ when(ci.getId()).thenReturn(ORG1_USER_ID);
+ CompositeKey ck =
+ createCompositeKey(
+ stub, "", ContractConstants.APPROVAL_PREFIX, ORG1_USER_ID, ORG2_USER_ID);
+ when(stub.getStringState(ck.toString())).thenReturn("true");
+ boolean value = this.contract.IsApprovedForAll(ctx, ORG1_USER_ID, ORG2_USER_ID);
+ assertThat(value).isEqualTo(true);
+ }
+
+ @Test
+ public void invokeGetApproveAllWithoutInitializeTest() {
+ setOrg1MspId(this.ci);
+ when(this.ci.getId()).thenReturn(ORG1_USER_ID);
+ Throwable thrown =
+ catchThrowable(
+ () -> {
+ this.contract.IsApprovedForAll(ctx, ORG1_USER_ID, ORG2_USER_ID);
+ });
+ assertThat(thrown)
+ .isInstanceOf(ChaincodeException.class)
+ .hasNoCause()
+ .hasMessage(
+ "Contract options need to be set before calling any function, call Initialize() to initialize contract");
+ }
+
+ @Test
+ public void invokeBroadcastTokenExistanceTest() {
+ setTokenNameAndSysmbol(this.stub);
+ setOrg1MspId(this.ci);
+ when(ci.getId()).thenReturn(ORG1_USER_ID);
+ CompositeKey ck =
+ createCompositeKey(
+ stub, "", ContractConstants.APPROVAL_PREFIX, ORG1_USER_ID, ORG2_USER_ID);
+ when(stub.getStringState(ck.toString())).thenReturn("true");
+ this.contract.BroadcastTokenExistance(ctx, 1);
+ TransferSingle transferSingleEvent = new TransferSingle(ORG1_USER_ID, "0x0", "0x0", 1, 0);
+ verify(this.stub)
+ .setEvent("TransferSingle", new Genson().serialize(transferSingleEvent).getBytes(UTF_8));
+ }
+
+ @Test
+ public void getBalanceOfTest() {
+ setTokenNameAndSysmbol(this.stub);
+ setOrg1MspId(this.ci);
+ when(ci.getId()).thenReturn(ORG1_USER_ID);
+ createCompositeKey(stub, "", ContractConstants.BALANCE_PREFIX, ORG1_USER_ID, "1");
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"));
+ long balance = this.contract.BalanceOf(ctx, ORG1_USER_ID, 1);
+ assertThat(balance).isEqualTo(10);
+ }
+
+ @Test
+ public void getClientAccountBalanceTest() {
+ setTokenNameAndSysmbol(this.stub);
+ setOrg1MspId(this.ci);
+ when(ci.getId()).thenReturn(ORG1_USER_ID);
+ createCompositeKey(stub, "", ContractConstants.BALANCE_PREFIX, ORG1_USER_ID, "1");
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"));
+ long balance = this.contract.ClientAccountBalance(ctx, 1);
+ assertThat(balance).isEqualTo(10);
+ }
+
+ @Test
+ public void getBalanceOfBatchTest() {
+
+ setTokenNameAndSysmbol(this.stub);
+ setOrg1MspId(this.ci);
+ when(ci.getId()).thenReturn(ORG1_USER_ID);
+ createCompositeKey(stub, "", ContractConstants.BALANCE_PREFIX, ORG1_USER_ID, "1");
+ createCompositeKey(stub, "", ContractConstants.BALANCE_PREFIX, ORG2_USER_ID, "2");
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG1_USER_ID, "1"},
+ new MockKeyValue("1", "10"));
+ setResultsIterator(
+ stub,
+ ContractConstants.BALANCE_PREFIX,
+ new String[] {ORG2_USER_ID, "2"},
+ new MockKeyValue("2", "20"));
+ long[] balance =
+ this.contract.BalanceOfBatch(
+ ctx, new String[] {ORG1_USER_ID, ORG2_USER_ID}, new long[] {1, 2});
+ assertThat(balance.length).isEqualTo(2);
+ assertThat(balance[0]).isEqualTo(10);
+ assertThat(balance[1]).isEqualTo(20);
+ }
+ }
+}
diff --git a/token-erc-1155/chaincode-java/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/token-erc-1155/chaincode-java/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100755
index 00000000..ca6ee9ce
--- /dev/null
+++ b/token-erc-1155/chaincode-java/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
\ No newline at end of file
diff --git a/token-erc-721/chaincode-java/config/checkstyle/checkstyle.xml b/token-erc-721/chaincode-java/config/checkstyle/checkstyle.xml
old mode 100644
new mode 100755
diff --git a/token-erc-721/chaincode-java/config/checkstyle/suppressions.xml b/token-erc-721/chaincode-java/config/checkstyle/suppressions.xml
old mode 100644
new mode 100755
diff --git a/token-erc-721/chaincode-java/gradle/wrapper/gradle-wrapper.jar b/token-erc-721/chaincode-java/gradle/wrapper/gradle-wrapper.jar
old mode 100644
new mode 100755
diff --git a/token-erc-721/chaincode-java/gradle/wrapper/gradle-wrapper.properties b/token-erc-721/chaincode-java/gradle/wrapper/gradle-wrapper.properties
old mode 100644
new mode 100755