mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-20 16:45:09 +00:00
Chaincodes update
Eight chaincode & change Endorsement setting
This commit is contained in:
parent
f697550d02
commit
456cf315ba
100 changed files with 30272 additions and 4 deletions
|
|
@ -0,0 +1 @@
|
|||
node_modules
|
||||
16
asset-transfer-basic/chaincode-typescript-evc/.gitignore
vendored
Normal file
16
asset-transfer-basic/chaincode-typescript-evc/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
package-lock.json
|
||||
|
||||
# Compiled TypeScript files
|
||||
dist
|
||||
|
||||
36
asset-transfer-basic/chaincode-typescript-evc/Dockerfile
Normal file
36
asset-transfer-basic/chaincode-typescript-evc/Dockerfile
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
FROM node:16 AS builder
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy node.js source and build, changing owner as well
|
||||
COPY --chown=node:node . /usr/src/app
|
||||
ENV npm_config_cache=/usr/src/app
|
||||
RUN npm ci && npm run package
|
||||
|
||||
|
||||
FROM node:16 AS production
|
||||
ARG CC_SERVER_PORT
|
||||
|
||||
# Setup tini to work better handle signals
|
||||
ENV TINI_VERSION v0.19.0
|
||||
ENV PLATFORM=amd64
|
||||
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-${PLATFORM} /tini
|
||||
RUN chmod +x /tini
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY --chown=node:node --from=builder /usr/src/app/dist ./dist
|
||||
COPY --chown=node:node --from=builder /usr/src/app/package.json ./
|
||||
COPY --chown=node:node --from=builder /usr/src/app/npm-shrinkwrap.json ./
|
||||
COPY --chown=node:node docker/docker-entrypoint.sh /usr/src/app/docker-entrypoint.sh
|
||||
|
||||
RUN npm ci --omit=dev && npm cache clean --force
|
||||
|
||||
ENV PORT $CC_SERVER_PORT
|
||||
EXPOSE $CC_SERVER_PORT
|
||||
ENV NODE_ENV=production
|
||||
|
||||
USER node
|
||||
ENTRYPOINT [ "/tini", "--", "/usr/src/app/docker-entrypoint.sh" ]
|
||||
16
asset-transfer-basic/chaincode-typescript-evc/docker/docker-entrypoint.sh
Executable file
16
asset-transfer-basic/chaincode-typescript-evc/docker/docker-entrypoint.sh
Executable file
|
|
@ -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
|
||||
npm run start:server-debug
|
||||
elif [ "${CORE_PEER_TLS_ENABLED,,}" = "true" ]; then
|
||||
npm run start:server
|
||||
else
|
||||
npm run start:server-nontls
|
||||
fi
|
||||
|
||||
2994
asset-transfer-basic/chaincode-typescript-evc/npm-shrinkwrap.json
generated
Normal file
2994
asset-transfer-basic/chaincode-typescript-evc/npm-shrinkwrap.json
generated
Normal file
File diff suppressed because it is too large
Load diff
69
asset-transfer-basic/chaincode-typescript-evc/package.json
Normal file
69
asset-transfer-basic/chaincode-typescript-evc/package.json
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"name": "asset-transfer-basic",
|
||||
"version": "1.0.0",
|
||||
"description": "Asset Transfer Basic contract implemented in TypeScript",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=5"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "tslint -c tslint.json 'src/**/*.ts'",
|
||||
"pretest": "npm run lint",
|
||||
"test": "nyc mocha -r ts-node/register src/**/*.spec.ts",
|
||||
"start": "set -x && fabric-chaincode-node start",
|
||||
"build": "tsc",
|
||||
"build:watch": "tsc -w",
|
||||
"prepublishOnly": "npm run build",
|
||||
"docker": "docker build -f ./Dockerfile -t asset-transfer-basic .",
|
||||
"package": "npm run build && npm shrinkwrap",
|
||||
"start:server-nontls": "set -x && fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID",
|
||||
"start:server-debug": "set -x && NODE_OPTIONS='--inspect=0.0.0.0:9229' fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID",
|
||||
"start:server": "set -x && fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID --chaincode-tls-key-file=/hyperledger/privatekey.pem --chaincode-tls-client-cacert-file=/hyperledger/rootcert.pem --chaincode-tls-cert-file=/hyperledger/cert.pem"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"author": "Hyperledger",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"fabric-contract-api": "^2.4.0",
|
||||
"fabric-shim": "^2.4.0",
|
||||
"json-stringify-deterministic": "^1.0.1",
|
||||
"sort-keys-recursive": "^2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^12.20.55",
|
||||
"@types/sinon": "^5.0.7",
|
||||
"@types/sinon-chai": "^3.2.1",
|
||||
"chai": "^4.2.0",
|
||||
"mocha": "^10.0.0",
|
||||
"nyc": "^14.1.1",
|
||||
"sinon": "^7.1.1",
|
||||
"sinon-chai": "^3.3.0",
|
||||
"ts-node": "^7.0.1",
|
||||
"tslint": "^5.11.0",
|
||||
"typescript": "^4.4"
|
||||
},
|
||||
"nyc": {
|
||||
"extension": [
|
||||
".ts",
|
||||
".tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"coverage/**",
|
||||
"dist/**"
|
||||
],
|
||||
"reporter": [
|
||||
"text-summary",
|
||||
"html"
|
||||
],
|
||||
"all": true,
|
||||
"check-coverage": true,
|
||||
"statements": 100,
|
||||
"branches": 100,
|
||||
"functions": 100,
|
||||
"lines": 100
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
// Deterministic JSON.stringify()
|
||||
import {Context, Contract, Info, Returns, Transaction} from 'fabric-contract-api';
|
||||
import stringify from 'json-stringify-deterministic';
|
||||
import sortKeysRecursive from 'sort-keys-recursive';
|
||||
import {Farmer} from './farmer';
|
||||
|
||||
@Info({title: 'AssetTransfer', description: 'Smart contract for trading assets'})
|
||||
export class AssetTransferContract extends Contract {
|
||||
|
||||
@Transaction()
|
||||
public async InitLedger(ctx: Context): Promise<void> {
|
||||
const assets: Farmer[] = [
|
||||
{
|
||||
FarmerID: 'asset1',
|
||||
FarmerName: 'farmer 1',
|
||||
PoNumber: 'PO0001',
|
||||
ItemType: 'coffee',
|
||||
Qty: 3,
|
||||
Location: 'Location 1',
|
||||
PurchasePrice: 100000,
|
||||
PurchaseDate: "2024-05-24 10:00:00"
|
||||
},{
|
||||
FarmerID: 'asset2',
|
||||
FarmerName: 'farmer 2',
|
||||
PoNumber: 'PO0002',
|
||||
ItemType: 'arabika coffee',
|
||||
Qty: 3,
|
||||
Location: 'Location 2',
|
||||
PurchasePrice: 100000,
|
||||
PurchaseDate: "2024-05-24 10:00:00"
|
||||
},
|
||||
];
|
||||
|
||||
for (const asset of assets) {
|
||||
// example of how to write to world state deterministically
|
||||
// use convetion of alphabetic order
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
// when retrieving data, in any lang, the order of data will be the same and consequently also the corresonding hash
|
||||
await ctx.stub.putState(asset.FarmerID, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
console.info(`Asset ${asset.FarmerID} initialized`);
|
||||
}
|
||||
}
|
||||
|
||||
// CreateAsset issues a new asset to the world state with given details.
|
||||
@Transaction()
|
||||
public async CreateAsset(ctx: Context, farmerId: string, farmerName: string, poNumber: string, itemType: string, qty: number, location: string, purchasePrice: number, purchaseDate: string): Promise<void> {
|
||||
const exists = await this.AssetExists(ctx, farmerId);
|
||||
if (exists) {
|
||||
throw new Error(`The asset ${farmerId} already exists`);
|
||||
}
|
||||
|
||||
const asset = {
|
||||
FarmerID: farmerId,
|
||||
FarmerName: farmerName,
|
||||
PoNumber: poNumber,
|
||||
ItemType: itemType,
|
||||
Qty: qty,
|
||||
Location: location,
|
||||
PurchasePrice: purchasePrice,
|
||||
PurchaseDate: purchaseDate,
|
||||
};
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(farmerId, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
}
|
||||
|
||||
// ReadAsset returns the asset stored in the world state with given id.
|
||||
@Transaction(false)
|
||||
public async ReadAsset(ctx: Context, farmerId: string): Promise<string> {
|
||||
const assetJSON = await ctx.stub.getState(farmerId); // get the asset from chaincode state
|
||||
if (!assetJSON || assetJSON.length === 0) {
|
||||
throw new Error(`The asset ${farmerId} does not exist`);
|
||||
}
|
||||
return assetJSON.toString();
|
||||
}
|
||||
|
||||
// UpdateAsset updates an existing asset in the world state with provided parameters.
|
||||
@Transaction()
|
||||
public async UpdateAsset(ctx: Context, farmerId: string, farmerName: string, poNumber: string, itemType: string, qty: number, location: string, purchasePrice: number, purchaseDate: Date): Promise<void> {
|
||||
const exists = await this.AssetExists(ctx, farmerId);
|
||||
if (!exists) {
|
||||
throw new Error(`The asset ${farmerId} does not exist`);
|
||||
}
|
||||
|
||||
// overwriting original asset with new asset
|
||||
const updatedAsset = {
|
||||
FarmerID: farmerId,
|
||||
FarmerName: farmerName,
|
||||
PoNumber: poNumber,
|
||||
ItemType: itemType,
|
||||
Qty: qty,
|
||||
Location: location,
|
||||
PurchasePrice: purchasePrice,
|
||||
PurchaseDate: purchaseDate,
|
||||
};
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
return ctx.stub.putState(farmerId, Buffer.from(stringify(sortKeysRecursive(updatedAsset))));
|
||||
}
|
||||
|
||||
// DeleteAsset deletes an given asset from the world state.
|
||||
@Transaction()
|
||||
public async DeleteAsset(ctx: Context, farmerId: string): Promise<void> {
|
||||
const exists = await this.AssetExists(ctx, farmerId);
|
||||
if (!exists) {
|
||||
throw new Error(`The asset ${farmerId} does not exist`);
|
||||
}
|
||||
return ctx.stub.deleteState(farmerId);
|
||||
}
|
||||
|
||||
// AssetExists returns true when asset with given ID exists in world state.
|
||||
@Transaction(false)
|
||||
@Returns('boolean')
|
||||
public async AssetExists(ctx: Context, farmerId: string): Promise<boolean> {
|
||||
const assetJSON = await ctx.stub.getState(farmerId);
|
||||
return assetJSON && assetJSON.length > 0;
|
||||
}
|
||||
|
||||
// TransferAsset updates the owner field of asset with given id in the world state, and returns the old owner.
|
||||
@Transaction()
|
||||
public async TransferAsset(ctx: Context, farmerId: string, purchasePrice: number): Promise<string> {
|
||||
const assetString = await this.ReadAsset(ctx, farmerId);
|
||||
const asset = JSON.parse(assetString);
|
||||
const oldOwner = asset.PurchasePrice;
|
||||
asset.PurchasePrice = purchasePrice;
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(farmerId, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
return oldOwner;
|
||||
}
|
||||
|
||||
// GetAllAssets returns all assets found in the world state.
|
||||
@Transaction(false)
|
||||
@Returns('string')
|
||||
public async GetAllAssets(ctx: Context): Promise<string> {
|
||||
const allResults = [];
|
||||
// range query with empty string for startKey and endKey does an open-ended query of all assets in the chaincode namespace.
|
||||
const iterator = await ctx.stub.getStateByRange('', '');
|
||||
let result = await iterator.next();
|
||||
while (!result.done) {
|
||||
const strValue = Buffer.from(result.value.value.toString()).toString('utf8');
|
||||
let record;
|
||||
try {
|
||||
record = JSON.parse(strValue);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
record = strValue;
|
||||
}
|
||||
allResults.push(record);
|
||||
result = await iterator.next();
|
||||
}
|
||||
return JSON.stringify(allResults);
|
||||
}
|
||||
|
||||
}
|
||||
33
asset-transfer-basic/chaincode-typescript-evc/src/farmer.ts
Normal file
33
asset-transfer-basic/chaincode-typescript-evc/src/farmer.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {Object, Property} from 'fabric-contract-api';
|
||||
|
||||
@Object()
|
||||
export class Farmer {
|
||||
|
||||
@Property()
|
||||
public FarmerID: string;
|
||||
|
||||
@Property()
|
||||
public FarmerName: string;
|
||||
|
||||
@Property()
|
||||
public PoNumber: string;
|
||||
|
||||
@Property()
|
||||
public ItemType: string;
|
||||
|
||||
@Property()
|
||||
public Qty: number;
|
||||
|
||||
@Property()
|
||||
public Location: string;
|
||||
|
||||
@Property()
|
||||
public PurchasePrice: number;
|
||||
|
||||
@Property()
|
||||
public PurchaseDate: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {AssetTransferContract} from './assetTransfer';
|
||||
|
||||
export {AssetTransferContract} from './assetTransfer';
|
||||
|
||||
export const contracts: any[] = [AssetTransferContract];
|
||||
19
asset-transfer-basic/chaincode-typescript-evc/tsconfig.json
Normal file
19
asset-transfer-basic/chaincode-typescript-evc/tsconfig.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": "dist",
|
||||
"target": "es2017",
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"./src/**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
23
asset-transfer-basic/chaincode-typescript-evc/tslint.json
Normal file
23
asset-transfer-basic/chaincode-typescript-evc/tslint.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"defaultSeverity": "error",
|
||||
"extends": [
|
||||
"tslint:recommended"
|
||||
],
|
||||
"jsRules": {},
|
||||
"rules": {
|
||||
"indent": [true, "spaces", 4],
|
||||
"linebreak-style": [true, "LF"],
|
||||
"quotemark": [true, "single"],
|
||||
"semicolon": [true, "always"],
|
||||
"no-console": false,
|
||||
"curly": true,
|
||||
"triple-equals": true,
|
||||
"no-string-throw": true,
|
||||
"no-var-keyword": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"object-literal-key-quotes": [true, "as-needed"],
|
||||
"object-literal-sort-keys": false,
|
||||
"max-line-length": false
|
||||
},
|
||||
"rulesDirectory": []
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
node_modules
|
||||
16
asset-transfer-basic/chaincode-typescript-export-location/.gitignore
vendored
Normal file
16
asset-transfer-basic/chaincode-typescript-export-location/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
package-lock.json
|
||||
|
||||
# Compiled TypeScript files
|
||||
dist
|
||||
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
FROM node:16 AS builder
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy node.js source and build, changing owner as well
|
||||
COPY --chown=node:node . /usr/src/app
|
||||
ENV npm_config_cache=/usr/src/app
|
||||
RUN npm ci && npm run package
|
||||
|
||||
|
||||
FROM node:16 AS production
|
||||
ARG CC_SERVER_PORT
|
||||
|
||||
# Setup tini to work better handle signals
|
||||
ENV TINI_VERSION v0.19.0
|
||||
ENV PLATFORM=amd64
|
||||
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-${PLATFORM} /tini
|
||||
RUN chmod +x /tini
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY --chown=node:node --from=builder /usr/src/app/dist ./dist
|
||||
COPY --chown=node:node --from=builder /usr/src/app/package.json ./
|
||||
COPY --chown=node:node --from=builder /usr/src/app/npm-shrinkwrap.json ./
|
||||
COPY --chown=node:node docker/docker-entrypoint.sh /usr/src/app/docker-entrypoint.sh
|
||||
|
||||
RUN npm ci --omit=dev && npm cache clean --force
|
||||
|
||||
ENV PORT $CC_SERVER_PORT
|
||||
EXPOSE $CC_SERVER_PORT
|
||||
ENV NODE_ENV=production
|
||||
|
||||
USER node
|
||||
ENTRYPOINT [ "/tini", "--", "/usr/src/app/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
|
||||
npm run start:server-debug
|
||||
elif [ "${CORE_PEER_TLS_ENABLED,,}" = "true" ]; then
|
||||
npm run start:server
|
||||
else
|
||||
npm run start:server-nontls
|
||||
fi
|
||||
|
||||
2973
asset-transfer-basic/chaincode-typescript-export-location/npm-shrinkwrap.json
generated
Normal file
2973
asset-transfer-basic/chaincode-typescript-export-location/npm-shrinkwrap.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"name": "asset-transfer-basic",
|
||||
"version": "1.0.0",
|
||||
"description": "Asset Transfer Basic contract implemented in TypeScript",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=5"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "tslint -c tslint.json 'src/**/*.ts'",
|
||||
"pretest": "npm run lint",
|
||||
"test": "nyc mocha -r ts-node/register src/**/*.spec.ts",
|
||||
"start": "set -x && fabric-chaincode-node start",
|
||||
"build": "tsc",
|
||||
"build:watch": "tsc -w",
|
||||
"prepublishOnly": "npm run build",
|
||||
"docker": "docker build -f ./Dockerfile -t asset-transfer-basic .",
|
||||
"package": "npm run build && npm shrinkwrap",
|
||||
"start:server-nontls": "set -x && fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID",
|
||||
"start:server-debug": "set -x && NODE_OPTIONS='--inspect=0.0.0.0:9229' fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID",
|
||||
"start:server": "set -x && fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID --chaincode-tls-key-file=/hyperledger/privatekey.pem --chaincode-tls-client-cacert-file=/hyperledger/rootcert.pem --chaincode-tls-cert-file=/hyperledger/cert.pem"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"author": "Hyperledger",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"fabric-contract-api": "^2.4.0",
|
||||
"fabric-shim": "^2.4.0",
|
||||
"json-stringify-deterministic": "^1.0.1",
|
||||
"sort-keys-recursive": "^2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^12.20.55",
|
||||
"@types/sinon": "^5.0.7",
|
||||
"@types/sinon-chai": "^3.2.1",
|
||||
"chai": "^4.2.0",
|
||||
"mocha": "^10.0.0",
|
||||
"nyc": "^14.1.1",
|
||||
"sinon": "^7.1.1",
|
||||
"sinon-chai": "^3.3.0",
|
||||
"ts-node": "^7.0.1",
|
||||
"tslint": "^5.11.0",
|
||||
"typescript": "^4.4"
|
||||
},
|
||||
"nyc": {
|
||||
"extension": [
|
||||
".ts",
|
||||
".tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"coverage/**",
|
||||
"dist/**"
|
||||
],
|
||||
"reporter": [
|
||||
"text-summary",
|
||||
"html"
|
||||
],
|
||||
"all": true,
|
||||
"check-coverage": true,
|
||||
"statements": 100,
|
||||
"branches": 100,
|
||||
"functions": 100,
|
||||
"lines": 100
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
// Deterministic JSON.stringify()
|
||||
import {Context, Contract, Info, Returns, Transaction} from 'fabric-contract-api';
|
||||
import stringify from 'json-stringify-deterministic';
|
||||
import sortKeysRecursive from 'sort-keys-recursive';
|
||||
import { Location } from './location';
|
||||
|
||||
|
||||
@Info({title: 'ExportLocation', description: 'Smart contract for master location of EVC'})
|
||||
export class AssetTransferContract extends Contract {
|
||||
|
||||
@Transaction()
|
||||
public async Init(ctx: Context): Promise<void> {
|
||||
const assets: Location[] = [
|
||||
{
|
||||
ID: 'EVC01',
|
||||
Name: 'Kebet',
|
||||
Latitude: '4.634243',
|
||||
Longitude: '96.832592',
|
||||
Actor: 'Export'
|
||||
},
|
||||
];
|
||||
|
||||
for (const asset of assets) {
|
||||
// example of how to write to world state deterministically
|
||||
// use convetion of alphabetic order
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
// when retrieving data, in any lang, the order of data will be the same and consequently also the corresonding hash
|
||||
await ctx.stub.putState(asset.ID, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
console.info(`Asset ${asset.ID} initialized`);
|
||||
}
|
||||
}
|
||||
|
||||
// CreateAsset issues a new asset to the world state with given details.
|
||||
@Transaction()
|
||||
public async CreateAsset(ctx: Context, evcCode: string, name: string, latitude: string, longitude: string): Promise<void> {
|
||||
|
||||
const id = evcCode;
|
||||
const exist = await this.AssetExists(ctx, id);
|
||||
if (exist) {
|
||||
throw new Error(`The Location of EVC code ${evcCode} already exists`);
|
||||
}
|
||||
|
||||
const asset = {
|
||||
ID: id,
|
||||
Name: name,
|
||||
Latitude: latitude,
|
||||
Longitude: longitude,
|
||||
Actor: 'Pulper'
|
||||
};
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
}
|
||||
|
||||
// ReadAsset returns the asset stored in the world state with given id.
|
||||
@Transaction(false)
|
||||
public async ReadAsset(ctx: Context, id: string): Promise<string> {
|
||||
const assetJSON = await ctx.stub.getState(id); // get the asset from chaincode state
|
||||
if (!assetJSON || assetJSON.length === 0) {
|
||||
throw new Error(`The location id ${id} does not exist`);
|
||||
}
|
||||
return assetJSON.toString();
|
||||
}
|
||||
|
||||
// UpdateAsset updates an existing asset in the world state with provided parameters.
|
||||
@Transaction()
|
||||
public async UpdateAsset(ctx: Context, evcCode:string, name: string, latitude: string, longitude: string): Promise<void> {
|
||||
const id = evcCode;
|
||||
const exist = await this.AssetExists(ctx, id);
|
||||
if (!exist) {
|
||||
throw new Error(`The Location of EVC code ${evcCode} does not exists`);
|
||||
}
|
||||
|
||||
// overwriting original asset with new asset
|
||||
const updatedAsset = {
|
||||
Name: name,
|
||||
Latitude: latitude,
|
||||
Longitude: longitude
|
||||
};
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
return ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(updatedAsset))));
|
||||
}
|
||||
|
||||
// DeleteAsset deletes an given asset from the world state.
|
||||
@Transaction()
|
||||
public async DeleteAsset(ctx: Context, evcCode: string): Promise<void> {
|
||||
const id = evcCode;
|
||||
const exist = await this.AssetExists(ctx, id);
|
||||
if (!exist) {
|
||||
throw new Error(`The Location of EVC code ${evcCode} does not exists`);
|
||||
}
|
||||
return ctx.stub.deleteState(id);
|
||||
}
|
||||
|
||||
// AssetExists returns true when asset with given ID exists in world state.
|
||||
@Transaction(false)
|
||||
@Returns('boolean')
|
||||
public async AssetExists(ctx: Context, id: string): Promise<boolean> {
|
||||
const assetJSON = await ctx.stub.getState(id);
|
||||
return assetJSON && assetJSON.length > 0;
|
||||
}
|
||||
|
||||
// TransferAsset updates the owner field of asset with given id in the world state, and returns the old owner.
|
||||
@Transaction()
|
||||
public async TransferAsset(ctx: Context, evcCode: string, latitude: string, longitude: string, newActor: string): Promise<string> {
|
||||
const id = evcCode;
|
||||
const assetString = await this.ReadAsset(ctx, id);
|
||||
const asset = JSON.parse(assetString);
|
||||
const oldActor = asset.Actor;
|
||||
asset.Actor = newActor;
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
return oldActor;
|
||||
}
|
||||
/*
|
||||
// TransferAsset updates the owner field of asset with given id in the world state, and returns the old owner.
|
||||
@Transaction()
|
||||
public async TransferAsset(ctx: Context, farmerId: string, newOwner: string): Promise<string> {
|
||||
const assetString = await this.ReadAsset(ctx, farmerId);
|
||||
const asset = JSON.parse(assetString);
|
||||
const oldOwner = asset.Owner;
|
||||
asset.Owner = newOwner;
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(farmerId, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
return oldOwner;
|
||||
}
|
||||
*/
|
||||
// GetAllAssets returns all assets found in the world state.
|
||||
@Transaction(false)
|
||||
@Returns('string')
|
||||
public async GetAllAssets(ctx: Context): Promise<string> {
|
||||
const allResults = [];
|
||||
// range query with empty string for startKey and endKey does an open-ended query of all assets in the chaincode namespace.
|
||||
const iterator = await ctx.stub.getStateByRange('', '');
|
||||
let result = await iterator.next();
|
||||
while (!result.done) {
|
||||
const strValue = Buffer.from(result.value.value.toString()).toString('utf8');
|
||||
let record;
|
||||
try {
|
||||
record = JSON.parse(strValue);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
record = strValue;
|
||||
}
|
||||
allResults.push(record);
|
||||
result = await iterator.next();
|
||||
}
|
||||
return JSON.stringify(allResults);
|
||||
}
|
||||
|
||||
protected randomID() {
|
||||
|
||||
let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
||||
var text = "";
|
||||
for (let i = 0; i < 100; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {AssetTransferContract} from './assetTransfer';
|
||||
|
||||
export {AssetTransferContract} from './assetTransfer';
|
||||
|
||||
export const contracts: any[] = [AssetTransferContract];
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {Object, Property} from 'fabric-contract-api';
|
||||
|
||||
@Object()
|
||||
export class Location {
|
||||
@Property()
|
||||
public ID: string;
|
||||
|
||||
@Property()
|
||||
public Name: string;
|
||||
|
||||
@Property()
|
||||
public Latitude: string;
|
||||
|
||||
@Property()
|
||||
public Longitude: string;
|
||||
|
||||
@Property()
|
||||
public Actor: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": "dist",
|
||||
"target": "es2017",
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"./src/**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"defaultSeverity": "error",
|
||||
"extends": [
|
||||
"tslint:recommended"
|
||||
],
|
||||
"jsRules": {},
|
||||
"rules": {
|
||||
"indent": [true, "spaces", 4],
|
||||
"linebreak-style": [true, "LF"],
|
||||
"quotemark": [true, "single"],
|
||||
"semicolon": [true, "always"],
|
||||
"no-console": false,
|
||||
"curly": true,
|
||||
"triple-equals": true,
|
||||
"no-string-throw": true,
|
||||
"no-var-keyword": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"object-literal-key-quotes": [true, "as-needed"],
|
||||
"object-literal-sort-keys": false,
|
||||
"max-line-length": false
|
||||
},
|
||||
"rulesDirectory": []
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
node_modules
|
||||
16
asset-transfer-basic/chaincode-typescript-farmer-location/.gitignore
vendored
Normal file
16
asset-transfer-basic/chaincode-typescript-farmer-location/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
package-lock.json
|
||||
|
||||
# Compiled TypeScript files
|
||||
dist
|
||||
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
FROM node:16 AS builder
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy node.js source and build, changing owner as well
|
||||
COPY --chown=node:node . /usr/src/app
|
||||
ENV npm_config_cache=/usr/src/app
|
||||
RUN npm ci && npm run package
|
||||
|
||||
|
||||
FROM node:16 AS production
|
||||
ARG CC_SERVER_PORT
|
||||
|
||||
# Setup tini to work better handle signals
|
||||
ENV TINI_VERSION v0.19.0
|
||||
ENV PLATFORM=amd64
|
||||
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-${PLATFORM} /tini
|
||||
RUN chmod +x /tini
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY --chown=node:node --from=builder /usr/src/app/dist ./dist
|
||||
COPY --chown=node:node --from=builder /usr/src/app/package.json ./
|
||||
COPY --chown=node:node --from=builder /usr/src/app/npm-shrinkwrap.json ./
|
||||
COPY --chown=node:node docker/docker-entrypoint.sh /usr/src/app/docker-entrypoint.sh
|
||||
|
||||
RUN npm ci --omit=dev && npm cache clean --force
|
||||
|
||||
ENV PORT $CC_SERVER_PORT
|
||||
EXPOSE $CC_SERVER_PORT
|
||||
ENV NODE_ENV=production
|
||||
|
||||
USER node
|
||||
ENTRYPOINT [ "/tini", "--", "/usr/src/app/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
|
||||
npm run start:server-debug
|
||||
elif [ "${CORE_PEER_TLS_ENABLED,,}" = "true" ]; then
|
||||
npm run start:server
|
||||
else
|
||||
npm run start:server-nontls
|
||||
fi
|
||||
|
||||
2973
asset-transfer-basic/chaincode-typescript-farmer-location/npm-shrinkwrap.json
generated
Normal file
2973
asset-transfer-basic/chaincode-typescript-farmer-location/npm-shrinkwrap.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"name": "asset-transfer-basic",
|
||||
"version": "1.0.0",
|
||||
"description": "Asset Transfer Basic contract implemented in TypeScript",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=5"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "tslint -c tslint.json 'src/**/*.ts'",
|
||||
"pretest": "npm run lint",
|
||||
"test": "nyc mocha -r ts-node/register src/**/*.spec.ts",
|
||||
"start": "set -x && fabric-chaincode-node start",
|
||||
"build": "tsc",
|
||||
"build:watch": "tsc -w",
|
||||
"prepublishOnly": "npm run build",
|
||||
"docker": "docker build -f ./Dockerfile -t asset-transfer-basic .",
|
||||
"package": "npm run build && npm shrinkwrap",
|
||||
"start:server-nontls": "set -x && fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID",
|
||||
"start:server-debug": "set -x && NODE_OPTIONS='--inspect=0.0.0.0:9229' fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID",
|
||||
"start:server": "set -x && fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID --chaincode-tls-key-file=/hyperledger/privatekey.pem --chaincode-tls-client-cacert-file=/hyperledger/rootcert.pem --chaincode-tls-cert-file=/hyperledger/cert.pem"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"author": "Hyperledger",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"fabric-contract-api": "^2.4.0",
|
||||
"fabric-shim": "^2.4.0",
|
||||
"json-stringify-deterministic": "^1.0.1",
|
||||
"sort-keys-recursive": "^2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^12.20.55",
|
||||
"@types/sinon": "^5.0.7",
|
||||
"@types/sinon-chai": "^3.2.1",
|
||||
"chai": "^4.2.0",
|
||||
"mocha": "^10.0.0",
|
||||
"nyc": "^14.1.1",
|
||||
"sinon": "^7.1.1",
|
||||
"sinon-chai": "^3.3.0",
|
||||
"ts-node": "^7.0.1",
|
||||
"tslint": "^5.11.0",
|
||||
"typescript": "^4.4"
|
||||
},
|
||||
"nyc": {
|
||||
"extension": [
|
||||
".ts",
|
||||
".tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"coverage/**",
|
||||
"dist/**"
|
||||
],
|
||||
"reporter": [
|
||||
"text-summary",
|
||||
"html"
|
||||
],
|
||||
"all": true,
|
||||
"check-coverage": true,
|
||||
"statements": 100,
|
||||
"branches": 100,
|
||||
"functions": 100,
|
||||
"lines": 100
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
// Deterministic JSON.stringify()
|
||||
import {Context, Contract, Info, Returns, Transaction} from 'fabric-contract-api';
|
||||
import stringify from 'json-stringify-deterministic';
|
||||
import sortKeysRecursive from 'sort-keys-recursive';
|
||||
import { Location } from './location';
|
||||
|
||||
|
||||
@Info({title: 'FarmerLocation', description: 'Smart contract for master location of farmers'})
|
||||
export class AssetTransferContract extends Contract {
|
||||
|
||||
@Transaction()
|
||||
public async Init(ctx: Context): Promise<void> {
|
||||
const assets: Location[] = [
|
||||
{
|
||||
ID: '4.4436935'+"|"+'96.7761686',
|
||||
Name: 'Atu Lintang',
|
||||
Latitude: '4.4436935',
|
||||
Longitude: '96.7761686',
|
||||
Actor: 'Farmer'
|
||||
},
|
||||
];
|
||||
|
||||
for (const asset of assets) {
|
||||
// example of how to write to world state deterministically
|
||||
// use convetion of alphabetic order
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
// when retrieving data, in any lang, the order of data will be the same and consequently also the corresonding hash
|
||||
await ctx.stub.putState(asset.ID, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
console.info(`Asset ${asset.ID} initialized`);
|
||||
}
|
||||
}
|
||||
|
||||
// CreateAsset issues a new asset to the world state with given details.
|
||||
@Transaction()
|
||||
public async CreateAsset(ctx: Context, name: string, latitude: string, longitude: string): Promise<void> {
|
||||
|
||||
const id = latitude+"|"+longitude;
|
||||
const exist = await this.AssetExists(ctx, id);
|
||||
if (exist) {
|
||||
throw new Error(`The Location with latitude ${latitude} and Longitude ${longitude} already exists`);
|
||||
}
|
||||
|
||||
const asset = {
|
||||
ID: id,
|
||||
Name: name,
|
||||
Latitude: latitude,
|
||||
Longitude: longitude,
|
||||
Actor: 'Farmer'
|
||||
};
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
}
|
||||
|
||||
// ReadAsset returns the asset stored in the world state with given id.
|
||||
@Transaction(false)
|
||||
public async ReadAsset(ctx: Context, id: string): Promise<string> {
|
||||
const assetJSON = await ctx.stub.getState(id); // get the asset from chaincode state
|
||||
if (!assetJSON || assetJSON.length === 0) {
|
||||
throw new Error(`The location id ${id} does not exist`);
|
||||
}
|
||||
return assetJSON.toString();
|
||||
}
|
||||
|
||||
// UpdateAsset updates an existing asset in the world state with provided parameters.
|
||||
@Transaction()
|
||||
public async UpdateAsset(ctx: Context, name: string, latitude: string, longitude: string): Promise<void> {
|
||||
const id = latitude+"|"+longitude;
|
||||
const exist = await this.AssetExists(ctx, id);
|
||||
if (!exist) {
|
||||
throw new Error(`The Location with latitude ${latitude} and Longitude ${longitude} does not exist`);
|
||||
}
|
||||
|
||||
// overwriting original asset with new asset
|
||||
const updatedAsset = {
|
||||
Name: name,
|
||||
Latitude: latitude,
|
||||
Longitude: longitude
|
||||
};
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
return ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(updatedAsset))));
|
||||
}
|
||||
|
||||
// DeleteAsset deletes an given asset from the world state.
|
||||
@Transaction()
|
||||
public async DeleteAsset(ctx: Context, latitude: string, longitude: string): Promise<void> {
|
||||
const id = latitude+"|"+longitude;
|
||||
const exist = await this.AssetExists(ctx, id);
|
||||
if (!exist) {
|
||||
throw new Error(`The Location with latitude ${latitude} and Longitude ${longitude} does not exist`);
|
||||
}
|
||||
return ctx.stub.deleteState(id);
|
||||
}
|
||||
|
||||
// AssetExists returns true when asset with given ID exists in world state.
|
||||
@Transaction(false)
|
||||
@Returns('boolean')
|
||||
public async AssetExists(ctx: Context, id: string): Promise<boolean> {
|
||||
const assetJSON = await ctx.stub.getState(id);
|
||||
return assetJSON && assetJSON.length > 0;
|
||||
}
|
||||
|
||||
// TransferAsset updates the owner field of asset with given id in the world state, and returns the old owner.
|
||||
@Transaction()
|
||||
public async TransferAsset(ctx: Context, latitude: string, longitude: string, newActor: string): Promise<string> {
|
||||
const id = latitude+"|"+longitude;
|
||||
const assetString = await this.ReadAsset(ctx, id);
|
||||
const asset = JSON.parse(assetString);
|
||||
const oldActor = asset.Actor;
|
||||
asset.Actor = newActor;
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
return oldActor;
|
||||
}
|
||||
/*
|
||||
// TransferAsset updates the owner field of asset with given id in the world state, and returns the old owner.
|
||||
@Transaction()
|
||||
public async TransferAsset(ctx: Context, farmerId: string, newOwner: string): Promise<string> {
|
||||
const assetString = await this.ReadAsset(ctx, farmerId);
|
||||
const asset = JSON.parse(assetString);
|
||||
const oldOwner = asset.Owner;
|
||||
asset.Owner = newOwner;
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(farmerId, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
return oldOwner;
|
||||
}
|
||||
*/
|
||||
// GetAllAssets returns all assets found in the world state.
|
||||
@Transaction(false)
|
||||
@Returns('string')
|
||||
public async GetAllAssets(ctx: Context): Promise<string> {
|
||||
const allResults = [];
|
||||
// range query with empty string for startKey and endKey does an open-ended query of all assets in the chaincode namespace.
|
||||
const iterator = await ctx.stub.getStateByRange('', '');
|
||||
let result = await iterator.next();
|
||||
while (!result.done) {
|
||||
const strValue = Buffer.from(result.value.value.toString()).toString('utf8');
|
||||
let record;
|
||||
try {
|
||||
record = JSON.parse(strValue);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
record = strValue;
|
||||
}
|
||||
allResults.push(record);
|
||||
result = await iterator.next();
|
||||
}
|
||||
return JSON.stringify(allResults);
|
||||
}
|
||||
|
||||
protected randomID() {
|
||||
|
||||
let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
||||
var text = "";
|
||||
for (let i = 0; i < 100; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {AssetTransferContract} from './assetTransfer';
|
||||
|
||||
export {AssetTransferContract} from './assetTransfer';
|
||||
|
||||
export const contracts: any[] = [AssetTransferContract];
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {Object, Property} from 'fabric-contract-api';
|
||||
|
||||
@Object()
|
||||
export class Location {
|
||||
@Property()
|
||||
public ID: string;
|
||||
|
||||
@Property()
|
||||
public Name: string;
|
||||
|
||||
@Property()
|
||||
public Latitude: string;
|
||||
|
||||
@Property()
|
||||
public Longitude: string;
|
||||
|
||||
@Property()
|
||||
public Actor: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": "dist",
|
||||
"target": "es2017",
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"./src/**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"defaultSeverity": "error",
|
||||
"extends": [
|
||||
"tslint:recommended"
|
||||
],
|
||||
"jsRules": {},
|
||||
"rules": {
|
||||
"indent": [true, "spaces", 4],
|
||||
"linebreak-style": [true, "LF"],
|
||||
"quotemark": [true, "single"],
|
||||
"semicolon": [true, "always"],
|
||||
"no-console": false,
|
||||
"curly": true,
|
||||
"triple-equals": true,
|
||||
"no-string-throw": true,
|
||||
"no-var-keyword": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"object-literal-key-quotes": [true, "as-needed"],
|
||||
"object-literal-sort-keys": false,
|
||||
"max-line-length": false
|
||||
},
|
||||
"rulesDirectory": []
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
node_modules
|
||||
16
asset-transfer-basic/chaincode-typescript-farmer-to-pulper/.gitignore
vendored
Normal file
16
asset-transfer-basic/chaincode-typescript-farmer-to-pulper/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
package-lock.json
|
||||
|
||||
# Compiled TypeScript files
|
||||
dist
|
||||
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
FROM node:16 AS builder
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy node.js source and build, changing owner as well
|
||||
COPY --chown=node:node . /usr/src/app
|
||||
ENV npm_config_cache=/usr/src/app
|
||||
RUN npm ci && npm run package
|
||||
|
||||
|
||||
FROM node:16 AS production
|
||||
ARG CC_SERVER_PORT
|
||||
|
||||
# Setup tini to work better handle signals
|
||||
ENV TINI_VERSION v0.19.0
|
||||
ENV PLATFORM=amd64
|
||||
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-${PLATFORM} /tini
|
||||
RUN chmod +x /tini
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY --chown=node:node --from=builder /usr/src/app/dist ./dist
|
||||
COPY --chown=node:node --from=builder /usr/src/app/package.json ./
|
||||
COPY --chown=node:node --from=builder /usr/src/app/npm-shrinkwrap.json ./
|
||||
COPY --chown=node:node docker/docker-entrypoint.sh /usr/src/app/docker-entrypoint.sh
|
||||
|
||||
RUN npm ci --omit=dev && npm cache clean --force
|
||||
|
||||
ENV PORT $CC_SERVER_PORT
|
||||
EXPOSE $CC_SERVER_PORT
|
||||
ENV NODE_ENV=production
|
||||
|
||||
USER node
|
||||
ENTRYPOINT [ "/tini", "--", "/usr/src/app/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
|
||||
npm run start:server-debug
|
||||
elif [ "${CORE_PEER_TLS_ENABLED,,}" = "true" ]; then
|
||||
npm run start:server
|
||||
else
|
||||
npm run start:server-nontls
|
||||
fi
|
||||
|
||||
2973
asset-transfer-basic/chaincode-typescript-farmer-to-pulper/npm-shrinkwrap.json
generated
Normal file
2973
asset-transfer-basic/chaincode-typescript-farmer-to-pulper/npm-shrinkwrap.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"name": "asset-transfer-basic",
|
||||
"version": "1.0.0",
|
||||
"description": "Asset Transfer Basic contract implemented in TypeScript",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=5"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "tslint -c tslint.json 'src/**/*.ts'",
|
||||
"pretest": "npm run lint",
|
||||
"test": "nyc mocha -r ts-node/register src/**/*.spec.ts",
|
||||
"start": "set -x && fabric-chaincode-node start",
|
||||
"build": "tsc",
|
||||
"build:watch": "tsc -w",
|
||||
"prepublishOnly": "npm run build",
|
||||
"docker": "docker build -f ./Dockerfile -t asset-transfer-basic .",
|
||||
"package": "npm run build && npm shrinkwrap",
|
||||
"start:server-nontls": "set -x && fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID",
|
||||
"start:server-debug": "set -x && NODE_OPTIONS='--inspect=0.0.0.0:9229' fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID",
|
||||
"start:server": "set -x && fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID --chaincode-tls-key-file=/hyperledger/privatekey.pem --chaincode-tls-client-cacert-file=/hyperledger/rootcert.pem --chaincode-tls-cert-file=/hyperledger/cert.pem"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"author": "Hyperledger",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"fabric-contract-api": "^2.4.0",
|
||||
"fabric-shim": "^2.4.0",
|
||||
"json-stringify-deterministic": "^1.0.1",
|
||||
"sort-keys-recursive": "^2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^12.20.55",
|
||||
"@types/sinon": "^5.0.7",
|
||||
"@types/sinon-chai": "^3.2.1",
|
||||
"chai": "^4.2.0",
|
||||
"mocha": "^10.0.0",
|
||||
"nyc": "^14.1.1",
|
||||
"sinon": "^7.1.1",
|
||||
"sinon-chai": "^3.3.0",
|
||||
"ts-node": "^7.0.1",
|
||||
"tslint": "^5.11.0",
|
||||
"typescript": "^4.4"
|
||||
},
|
||||
"nyc": {
|
||||
"extension": [
|
||||
".ts",
|
||||
".tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"coverage/**",
|
||||
"dist/**"
|
||||
],
|
||||
"reporter": [
|
||||
"text-summary",
|
||||
"html"
|
||||
],
|
||||
"all": true,
|
||||
"check-coverage": true,
|
||||
"statements": 100,
|
||||
"branches": 100,
|
||||
"functions": 100,
|
||||
"lines": 100
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
// Deterministic JSON.stringify()
|
||||
import {Context, Contract, Info, Returns, Transaction} from 'fabric-contract-api';
|
||||
import stringify from 'json-stringify-deterministic';
|
||||
import sortKeysRecursive from 'sort-keys-recursive';
|
||||
import {Farmer} from './farmer';
|
||||
|
||||
@Info({title: 'FarmerTransfer', description: 'Smart contract for trading assets'})
|
||||
export class AssetTransferContract extends Contract {
|
||||
|
||||
@Transaction()
|
||||
public async Init(ctx: Context): Promise<void> {
|
||||
const assets: Farmer[] = [
|
||||
{
|
||||
ID: 'PO/EVC03-VCH07B/0823/001'+'EVC01-VCH07C-VCP01'+'F01-01187'+'03-23/000151',
|
||||
PoNumber: 'PO/EVC03-VCH07B/0823/001',
|
||||
VcpCode: 'EVC01-VCH07C-VCP01',
|
||||
Location: 'Merah Muyang',
|
||||
FarmerID: 'F01-01187',
|
||||
FarmerName: 'Masmuda',
|
||||
PurchaseDate: "22-Aug-2023",
|
||||
ReceiptNo: '03-23/000151',
|
||||
ItemType: 'Cherry SW',
|
||||
ProcessType: 'Semi Washed',
|
||||
Qty: "75.00",
|
||||
PurchasePrice: "Rp 13,700",
|
||||
Floating: "7.76",
|
||||
BatchNumber: "1",
|
||||
Actor: 'Farmer'
|
||||
},
|
||||
];
|
||||
|
||||
for (const asset of assets) {
|
||||
// example of how to write to world state deterministically
|
||||
// use convetion of alphabetic order
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
// when retrieving data, in any lang, the order of data will be the same and consequently also the corresonding hash
|
||||
await ctx.stub.putState(asset.ID, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
console.info(`Asset ${asset.FarmerID} initialized`);
|
||||
}
|
||||
}
|
||||
|
||||
// CreateAsset issues a new asset to the world state with given details.
|
||||
@Transaction()
|
||||
public async CreateAsset(ctx: Context, farmerId: string, farmerName: string, poNumber: string,
|
||||
itemType: string, qty: string, location: string, purchasePrice: string, purchaseDate: string,
|
||||
processType: string, vcpCode: string, receiptNo: string, floating: string, batchNumber: string): Promise<void> {
|
||||
|
||||
const id = poNumber+vcpCode+farmerId+receiptNo;
|
||||
const exist = await this.AssetExists(ctx, id);
|
||||
if (exist) {
|
||||
throw new Error(`The farmer ID ${farmerId} with ${receiptNo}, ${vcpCode}, and ${poNumber} already exists`);
|
||||
}
|
||||
|
||||
const asset = {
|
||||
ID: id,
|
||||
VcpCode: vcpCode,
|
||||
ReceiptNo: receiptNo,
|
||||
ProcessType: processType,
|
||||
Floating: floating,
|
||||
BatchNumber: batchNumber,
|
||||
FarmerID: farmerId,
|
||||
FarmerName: farmerName,
|
||||
PoNumber: poNumber,
|
||||
ItemType: itemType,
|
||||
Qty: qty,
|
||||
Location: location,
|
||||
PurchasePrice: purchasePrice,
|
||||
PurchaseDate: purchaseDate,
|
||||
Actor: 'Farmer'
|
||||
};
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
}
|
||||
|
||||
// ReadAsset returns the asset stored in the world state with given id.
|
||||
@Transaction(false)
|
||||
public async ReadAsset(ctx: Context, id: string): Promise<string> {
|
||||
const assetJSON = await ctx.stub.getState(id); // get the asset from chaincode state
|
||||
if (!assetJSON || assetJSON.length === 0) {
|
||||
throw new Error(`The farmer id ${id} does not exist`);
|
||||
}
|
||||
return assetJSON.toString();
|
||||
}
|
||||
|
||||
// UpdateAsset updates an existing asset in the world state with provided parameters.
|
||||
@Transaction()
|
||||
public async UpdateAsset(ctx: Context, farmerId: string, farmerName: string, poNumber: string,
|
||||
itemType: string, qty: string, location: string, purchasePrice: string, purchaseDate: string,
|
||||
processType: string, vcpCode: string, receiptNo: string, floating: string, batchNumber: string): Promise<void> {
|
||||
const id = poNumber+vcpCode+farmerId+receiptNo;
|
||||
const exist = await this.AssetExists(ctx, id);
|
||||
if (!exist) {
|
||||
throw new Error(`The farmer ID ${farmerId} with ${receiptNo}, ${vcpCode}, and ${poNumber} does not exist`);
|
||||
}
|
||||
|
||||
// overwriting original asset with new asset
|
||||
const updatedAsset = {
|
||||
VcpCode: vcpCode,
|
||||
ReceiptNo: receiptNo,
|
||||
ProcessType: processType,
|
||||
Floating: floating,
|
||||
BatchNumber: batchNumber,
|
||||
FarmerID: farmerId,
|
||||
FarmerName: farmerName,
|
||||
PoNumber: poNumber,
|
||||
ItemType: itemType,
|
||||
Qty: qty,
|
||||
Location: location,
|
||||
PurchasePrice: purchasePrice,
|
||||
PurchaseDate: purchaseDate,
|
||||
};
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
return ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(updatedAsset))));
|
||||
}
|
||||
|
||||
// DeleteAsset deletes an given asset from the world state.
|
||||
@Transaction()
|
||||
public async DeleteAsset(ctx: Context, farmerId: string, receiptNo: string, vcpCode: string, poNumber: string): Promise<void> {
|
||||
const id = poNumber+vcpCode+farmerId+receiptNo;
|
||||
const exist = await this.AssetExists(ctx, id);
|
||||
if (!exist) {
|
||||
throw new Error(`The farmer ID ${farmerId} with ${receiptNo}, ${vcpCode}, and ${poNumber} does not exist`);
|
||||
}
|
||||
return ctx.stub.deleteState(id);
|
||||
}
|
||||
|
||||
// AssetExists returns true when asset with given ID exists in world state.
|
||||
@Transaction(false)
|
||||
@Returns('boolean')
|
||||
public async AssetExists(ctx: Context, id: string): Promise<boolean> {
|
||||
const assetJSON = await ctx.stub.getState(id);
|
||||
return assetJSON && assetJSON.length > 0;
|
||||
}
|
||||
|
||||
// TransferAsset updates the owner field of asset with given id in the world state, and returns the old owner.
|
||||
@Transaction()
|
||||
public async TransferAsset(ctx: Context, farmerId: string, receiptNo: string, vcpCode: string, poNumber: string, newActor: string): Promise<string> {
|
||||
const id = poNumber+vcpCode+farmerId+receiptNo;
|
||||
const assetString = await this.ReadAsset(ctx, id);
|
||||
const asset = JSON.parse(assetString);
|
||||
const oldActor = asset.Actor;
|
||||
asset.Actor = newActor;
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
return oldActor;
|
||||
}
|
||||
/*
|
||||
// TransferAsset updates the owner field of asset with given id in the world state, and returns the old owner.
|
||||
@Transaction()
|
||||
public async TransferAsset(ctx: Context, farmerId: string, newOwner: string): Promise<string> {
|
||||
const assetString = await this.ReadAsset(ctx, farmerId);
|
||||
const asset = JSON.parse(assetString);
|
||||
const oldOwner = asset.Owner;
|
||||
asset.Owner = newOwner;
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(farmerId, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
return oldOwner;
|
||||
}
|
||||
*/
|
||||
// GetAllAssets returns all assets found in the world state.
|
||||
@Transaction(false)
|
||||
@Returns('string')
|
||||
public async GetAllAssets(ctx: Context): Promise<string> {
|
||||
const allResults = [];
|
||||
// range query with empty string for startKey and endKey does an open-ended query of all assets in the chaincode namespace.
|
||||
const iterator = await ctx.stub.getStateByRange('', '');
|
||||
let result = await iterator.next();
|
||||
while (!result.done) {
|
||||
const strValue = Buffer.from(result.value.value.toString()).toString('utf8');
|
||||
let record;
|
||||
try {
|
||||
record = JSON.parse(strValue);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
record = strValue;
|
||||
}
|
||||
allResults.push(record);
|
||||
result = await iterator.next();
|
||||
}
|
||||
return JSON.stringify(allResults);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {Object, Property} from 'fabric-contract-api';
|
||||
|
||||
@Object()
|
||||
export class Farmer {
|
||||
@Property()
|
||||
public ID: string;
|
||||
|
||||
@Property()
|
||||
public PoNumber: string;
|
||||
|
||||
@Property()
|
||||
public VcpCode: string;
|
||||
|
||||
@Property()
|
||||
public Location: string;
|
||||
|
||||
@Property()
|
||||
public FarmerID: string;
|
||||
|
||||
@Property()
|
||||
public FarmerName: string;
|
||||
|
||||
@Property()
|
||||
public PurchaseDate: string;
|
||||
|
||||
@Property()
|
||||
public ReceiptNo: string;
|
||||
|
||||
@Property()
|
||||
public ItemType: string;
|
||||
|
||||
@Property()
|
||||
public ProcessType: string;
|
||||
|
||||
@Property()
|
||||
public Qty: string;
|
||||
|
||||
@Property()
|
||||
public PurchasePrice: string;
|
||||
|
||||
@Property()
|
||||
public Floating: string;
|
||||
|
||||
@Property()
|
||||
public BatchNumber: string;
|
||||
|
||||
@Property()
|
||||
public Actor: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {AssetTransferContract} from './assetTransfer';
|
||||
|
||||
export {AssetTransferContract} from './assetTransfer';
|
||||
|
||||
export const contracts: any[] = [AssetTransferContract];
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": "dist",
|
||||
"target": "es2017",
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"./src/**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"defaultSeverity": "error",
|
||||
"extends": [
|
||||
"tslint:recommended"
|
||||
],
|
||||
"jsRules": {},
|
||||
"rules": {
|
||||
"indent": [true, "spaces", 4],
|
||||
"linebreak-style": [true, "LF"],
|
||||
"quotemark": [true, "single"],
|
||||
"semicolon": [true, "always"],
|
||||
"no-console": false,
|
||||
"curly": true,
|
||||
"triple-equals": true,
|
||||
"no-string-throw": true,
|
||||
"no-var-keyword": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"object-literal-key-quotes": [true, "as-needed"],
|
||||
"object-literal-sort-keys": false,
|
||||
"max-line-length": false
|
||||
},
|
||||
"rulesDirectory": []
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
node_modules
|
||||
16
asset-transfer-basic/chaincode-typescript-farmer/.gitignore
vendored
Normal file
16
asset-transfer-basic/chaincode-typescript-farmer/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
package-lock.json
|
||||
|
||||
# Compiled TypeScript files
|
||||
dist
|
||||
|
||||
36
asset-transfer-basic/chaincode-typescript-farmer/Dockerfile
Normal file
36
asset-transfer-basic/chaincode-typescript-farmer/Dockerfile
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
FROM node:16 AS builder
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy node.js source and build, changing owner as well
|
||||
COPY --chown=node:node . /usr/src/app
|
||||
ENV npm_config_cache=/usr/src/app
|
||||
RUN npm ci && npm run package
|
||||
|
||||
|
||||
FROM node:16 AS production
|
||||
ARG CC_SERVER_PORT
|
||||
|
||||
# Setup tini to work better handle signals
|
||||
ENV TINI_VERSION v0.19.0
|
||||
ENV PLATFORM=amd64
|
||||
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-${PLATFORM} /tini
|
||||
RUN chmod +x /tini
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY --chown=node:node --from=builder /usr/src/app/dist ./dist
|
||||
COPY --chown=node:node --from=builder /usr/src/app/package.json ./
|
||||
COPY --chown=node:node --from=builder /usr/src/app/npm-shrinkwrap.json ./
|
||||
COPY --chown=node:node docker/docker-entrypoint.sh /usr/src/app/docker-entrypoint.sh
|
||||
|
||||
RUN npm ci --omit=dev && npm cache clean --force
|
||||
|
||||
ENV PORT $CC_SERVER_PORT
|
||||
EXPOSE $CC_SERVER_PORT
|
||||
ENV NODE_ENV=production
|
||||
|
||||
USER node
|
||||
ENTRYPOINT [ "/tini", "--", "/usr/src/app/docker-entrypoint.sh" ]
|
||||
16
asset-transfer-basic/chaincode-typescript-farmer/docker/docker-entrypoint.sh
Executable file
16
asset-transfer-basic/chaincode-typescript-farmer/docker/docker-entrypoint.sh
Executable file
|
|
@ -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
|
||||
npm run start:server-debug
|
||||
elif [ "${CORE_PEER_TLS_ENABLED,,}" = "true" ]; then
|
||||
npm run start:server
|
||||
else
|
||||
npm run start:server-nontls
|
||||
fi
|
||||
|
||||
2973
asset-transfer-basic/chaincode-typescript-farmer/npm-shrinkwrap.json
generated
Normal file
2973
asset-transfer-basic/chaincode-typescript-farmer/npm-shrinkwrap.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"name": "asset-transfer-basic",
|
||||
"version": "1.0.0",
|
||||
"description": "Asset Transfer Basic contract implemented in TypeScript",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=5"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "tslint -c tslint.json 'src/**/*.ts'",
|
||||
"pretest": "npm run lint",
|
||||
"test": "nyc mocha -r ts-node/register src/**/*.spec.ts",
|
||||
"start": "set -x && fabric-chaincode-node start",
|
||||
"build": "tsc",
|
||||
"build:watch": "tsc -w",
|
||||
"prepublishOnly": "npm run build",
|
||||
"docker": "docker build -f ./Dockerfile -t asset-transfer-basic .",
|
||||
"package": "npm run build && npm shrinkwrap",
|
||||
"start:server-nontls": "set -x && fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID",
|
||||
"start:server-debug": "set -x && NODE_OPTIONS='--inspect=0.0.0.0:9229' fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID",
|
||||
"start:server": "set -x && fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID --chaincode-tls-key-file=/hyperledger/privatekey.pem --chaincode-tls-client-cacert-file=/hyperledger/rootcert.pem --chaincode-tls-cert-file=/hyperledger/cert.pem"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"author": "Hyperledger",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"fabric-contract-api": "^2.4.0",
|
||||
"fabric-shim": "^2.4.0",
|
||||
"json-stringify-deterministic": "^1.0.1",
|
||||
"sort-keys-recursive": "^2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^12.20.55",
|
||||
"@types/sinon": "^5.0.7",
|
||||
"@types/sinon-chai": "^3.2.1",
|
||||
"chai": "^4.2.0",
|
||||
"mocha": "^10.0.0",
|
||||
"nyc": "^14.1.1",
|
||||
"sinon": "^7.1.1",
|
||||
"sinon-chai": "^3.3.0",
|
||||
"ts-node": "^7.0.1",
|
||||
"tslint": "^5.11.0",
|
||||
"typescript": "^4.4"
|
||||
},
|
||||
"nyc": {
|
||||
"extension": [
|
||||
".ts",
|
||||
".tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"coverage/**",
|
||||
"dist/**"
|
||||
],
|
||||
"reporter": [
|
||||
"text-summary",
|
||||
"html"
|
||||
],
|
||||
"all": true,
|
||||
"check-coverage": true,
|
||||
"statements": 100,
|
||||
"branches": 100,
|
||||
"functions": 100,
|
||||
"lines": 100
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
// Deterministic JSON.stringify()
|
||||
import {Context, Contract, Info, Returns, Transaction} from 'fabric-contract-api';
|
||||
import stringify from 'json-stringify-deterministic';
|
||||
import sortKeysRecursive from 'sort-keys-recursive';
|
||||
import {Farmer} from './farmer';
|
||||
|
||||
@Info({title: 'FarmerTransfer', description: 'Smart contract for trading assets'})
|
||||
export class AssetTransferContract extends Contract {
|
||||
|
||||
@Transaction()
|
||||
public async Init(ctx: Context): Promise<void> {
|
||||
const assets: Farmer[] = [
|
||||
{
|
||||
ID: 'DUMMYF001',
|
||||
FarmerName: 'Masmuda',
|
||||
Location: "Merah Muyang",
|
||||
Actor: 'Farmer'
|
||||
},
|
||||
];
|
||||
|
||||
for (const asset of assets) {
|
||||
// example of how to write to world state deterministically
|
||||
// use convetion of alphabetic order
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
// when retrieving data, in any lang, the order of data will be the same and consequently also the corresonding hash
|
||||
await ctx.stub.putState(asset.ID, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
console.info(`Asset ${asset.ID} initialized`);
|
||||
}
|
||||
}
|
||||
|
||||
// CreateAsset issues a new asset to the world state with given details.
|
||||
@Transaction()
|
||||
public async CreateAsset(ctx: Context, farmerId: string, farmerName: string, location: string): Promise<void> {
|
||||
|
||||
const exist = await this.AssetExists(ctx, farmerId);
|
||||
if (exist) {
|
||||
throw new Error(`The farmer ID ${farmerId} already exist`);
|
||||
}
|
||||
const asset = {
|
||||
ID: farmerId,
|
||||
FarmerName: farmerName,
|
||||
Location: location,
|
||||
Actor: 'Farmer'
|
||||
};
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(farmerId, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
|
||||
}
|
||||
|
||||
// ReadAsset returns the asset stored in the world state with given id.
|
||||
@Transaction(false)
|
||||
public async ReadAsset(ctx: Context, id: string): Promise<string> {
|
||||
const assetJSON = await ctx.stub.getState(id); // get the asset from chaincode state
|
||||
if (!assetJSON || assetJSON.length === 0) {
|
||||
throw new Error(`The farmer id ${id} does not exist`);
|
||||
}
|
||||
return assetJSON.toString();
|
||||
}
|
||||
|
||||
// UpdateAsset updates an existing asset in the world state with provided parameters.
|
||||
@Transaction()
|
||||
public async UpdateAsset(ctx: Context, farmerId: string, farmerName: string, location: string): Promise<void> {
|
||||
|
||||
const exist = await this.AssetExists(ctx, farmerId);
|
||||
if (!exist) {
|
||||
throw new Error(`The farmer ID ${farmerId} does not exist`);
|
||||
}
|
||||
|
||||
// overwriting original asset with new asset
|
||||
const updatedAsset = {
|
||||
FarmerName: farmerName,
|
||||
Location: location
|
||||
};
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
return ctx.stub.putState(farmerId, Buffer.from(stringify(sortKeysRecursive(updatedAsset))));
|
||||
}
|
||||
|
||||
// DeleteAsset deletes an given asset from the world state.
|
||||
@Transaction()
|
||||
public async DeleteAsset(ctx: Context, farmerId: string): Promise<void> {
|
||||
|
||||
const exist = await this.AssetExists(ctx, farmerId);
|
||||
if (!exist) {
|
||||
throw new Error(`The farmer ID ${farmerId} does not exist`);
|
||||
}
|
||||
return ctx.stub.deleteState(farmerId);
|
||||
}
|
||||
|
||||
// AssetExists returns true when asset with given ID exists in world state.
|
||||
@Transaction(false)
|
||||
@Returns('boolean')
|
||||
public async AssetExists(ctx: Context, id: string): Promise<boolean> {
|
||||
const assetJSON = await ctx.stub.getState(id);
|
||||
return assetJSON && assetJSON.length > 0;
|
||||
}
|
||||
|
||||
// TransferAsset updates the owner field of asset with given id in the world state, and returns the old owner.
|
||||
@Transaction()
|
||||
public async TransferAsset(ctx: Context, farmerId: string, newActor: string): Promise<string> {
|
||||
const assetString = await this.ReadAsset(ctx, farmerId);
|
||||
const asset = JSON.parse(assetString);
|
||||
const oldActor = asset.Actor;
|
||||
asset.Actor = newActor;
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(farmerId, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
return oldActor;
|
||||
}
|
||||
/*
|
||||
// TransferAsset updates the owner field of asset with given id in the world state, and returns the old owner.
|
||||
@Transaction()
|
||||
public async TransferAsset(ctx: Context, farmerId: string, newOwner: string): Promise<string> {
|
||||
const assetString = await this.ReadAsset(ctx, farmerId);
|
||||
const asset = JSON.parse(assetString);
|
||||
const oldOwner = asset.Owner;
|
||||
asset.Owner = newOwner;
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(farmerId, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
return oldOwner;
|
||||
}
|
||||
*/
|
||||
// GetAllAssets returns all assets found in the world state.
|
||||
@Transaction(false)
|
||||
@Returns('string')
|
||||
public async GetAllAssets(ctx: Context): Promise<string> {
|
||||
const allResults = [];
|
||||
// range query with empty string for startKey and endKey does an open-ended query of all assets in the chaincode namespace.
|
||||
const iterator = await ctx.stub.getStateByRange('', '');
|
||||
let result = await iterator.next();
|
||||
while (!result.done) {
|
||||
const strValue = Buffer.from(result.value.value.toString()).toString('utf8');
|
||||
let record;
|
||||
try {
|
||||
record = JSON.parse(strValue);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
record = strValue;
|
||||
}
|
||||
allResults.push(record);
|
||||
result = await iterator.next();
|
||||
}
|
||||
return JSON.stringify(allResults);
|
||||
}
|
||||
|
||||
protected randomID() {
|
||||
|
||||
let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
||||
var text = "";
|
||||
for (let i = 0; i < 100; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {Object, Property} from 'fabric-contract-api';
|
||||
|
||||
@Object()
|
||||
export class Farmer {
|
||||
@Property()
|
||||
public ID: string;
|
||||
|
||||
@Property()
|
||||
public FarmerName: string;
|
||||
|
||||
@Property()
|
||||
public Location: string;
|
||||
|
||||
@Property()
|
||||
public Actor: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {AssetTransferContract} from './assetTransfer';
|
||||
|
||||
export {AssetTransferContract} from './assetTransfer';
|
||||
|
||||
export const contracts: any[] = [AssetTransferContract];
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": "dist",
|
||||
"target": "es2017",
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"./src/**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
23
asset-transfer-basic/chaincode-typescript-farmer/tslint.json
Normal file
23
asset-transfer-basic/chaincode-typescript-farmer/tslint.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"defaultSeverity": "error",
|
||||
"extends": [
|
||||
"tslint:recommended"
|
||||
],
|
||||
"jsRules": {},
|
||||
"rules": {
|
||||
"indent": [true, "spaces", 4],
|
||||
"linebreak-style": [true, "LF"],
|
||||
"quotemark": [true, "single"],
|
||||
"semicolon": [true, "always"],
|
||||
"no-console": false,
|
||||
"curly": true,
|
||||
"triple-equals": true,
|
||||
"no-string-throw": true,
|
||||
"no-var-keyword": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"object-literal-key-quotes": [true, "as-needed"],
|
||||
"object-literal-sort-keys": false,
|
||||
"max-line-length": false
|
||||
},
|
||||
"rulesDirectory": []
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
node_modules
|
||||
16
asset-transfer-basic/chaincode-typescript-huller-location/.gitignore
vendored
Normal file
16
asset-transfer-basic/chaincode-typescript-huller-location/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
package-lock.json
|
||||
|
||||
# Compiled TypeScript files
|
||||
dist
|
||||
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
FROM node:16 AS builder
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy node.js source and build, changing owner as well
|
||||
COPY --chown=node:node . /usr/src/app
|
||||
ENV npm_config_cache=/usr/src/app
|
||||
RUN npm ci && npm run package
|
||||
|
||||
|
||||
FROM node:16 AS production
|
||||
ARG CC_SERVER_PORT
|
||||
|
||||
# Setup tini to work better handle signals
|
||||
ENV TINI_VERSION v0.19.0
|
||||
ENV PLATFORM=amd64
|
||||
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-${PLATFORM} /tini
|
||||
RUN chmod +x /tini
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY --chown=node:node --from=builder /usr/src/app/dist ./dist
|
||||
COPY --chown=node:node --from=builder /usr/src/app/package.json ./
|
||||
COPY --chown=node:node --from=builder /usr/src/app/npm-shrinkwrap.json ./
|
||||
COPY --chown=node:node docker/docker-entrypoint.sh /usr/src/app/docker-entrypoint.sh
|
||||
|
||||
RUN npm ci --omit=dev && npm cache clean --force
|
||||
|
||||
ENV PORT $CC_SERVER_PORT
|
||||
EXPOSE $CC_SERVER_PORT
|
||||
ENV NODE_ENV=production
|
||||
|
||||
USER node
|
||||
ENTRYPOINT [ "/tini", "--", "/usr/src/app/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
|
||||
npm run start:server-debug
|
||||
elif [ "${CORE_PEER_TLS_ENABLED,,}" = "true" ]; then
|
||||
npm run start:server
|
||||
else
|
||||
npm run start:server-nontls
|
||||
fi
|
||||
|
||||
2973
asset-transfer-basic/chaincode-typescript-huller-location/npm-shrinkwrap.json
generated
Normal file
2973
asset-transfer-basic/chaincode-typescript-huller-location/npm-shrinkwrap.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"name": "asset-transfer-basic",
|
||||
"version": "1.0.0",
|
||||
"description": "Asset Transfer Basic contract implemented in TypeScript",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=5"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "tslint -c tslint.json 'src/**/*.ts'",
|
||||
"pretest": "npm run lint",
|
||||
"test": "nyc mocha -r ts-node/register src/**/*.spec.ts",
|
||||
"start": "set -x && fabric-chaincode-node start",
|
||||
"build": "tsc",
|
||||
"build:watch": "tsc -w",
|
||||
"prepublishOnly": "npm run build",
|
||||
"docker": "docker build -f ./Dockerfile -t asset-transfer-basic .",
|
||||
"package": "npm run build && npm shrinkwrap",
|
||||
"start:server-nontls": "set -x && fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID",
|
||||
"start:server-debug": "set -x && NODE_OPTIONS='--inspect=0.0.0.0:9229' fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID",
|
||||
"start:server": "set -x && fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID --chaincode-tls-key-file=/hyperledger/privatekey.pem --chaincode-tls-client-cacert-file=/hyperledger/rootcert.pem --chaincode-tls-cert-file=/hyperledger/cert.pem"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"author": "Hyperledger",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"fabric-contract-api": "^2.4.0",
|
||||
"fabric-shim": "^2.4.0",
|
||||
"json-stringify-deterministic": "^1.0.1",
|
||||
"sort-keys-recursive": "^2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^12.20.55",
|
||||
"@types/sinon": "^5.0.7",
|
||||
"@types/sinon-chai": "^3.2.1",
|
||||
"chai": "^4.2.0",
|
||||
"mocha": "^10.0.0",
|
||||
"nyc": "^14.1.1",
|
||||
"sinon": "^7.1.1",
|
||||
"sinon-chai": "^3.3.0",
|
||||
"ts-node": "^7.0.1",
|
||||
"tslint": "^5.11.0",
|
||||
"typescript": "^4.4"
|
||||
},
|
||||
"nyc": {
|
||||
"extension": [
|
||||
".ts",
|
||||
".tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"coverage/**",
|
||||
"dist/**"
|
||||
],
|
||||
"reporter": [
|
||||
"text-summary",
|
||||
"html"
|
||||
],
|
||||
"all": true,
|
||||
"check-coverage": true,
|
||||
"statements": 100,
|
||||
"branches": 100,
|
||||
"functions": 100,
|
||||
"lines": 100
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
// Deterministic JSON.stringify()
|
||||
import {Context, Contract, Info, Returns, Transaction} from 'fabric-contract-api';
|
||||
import stringify from 'json-stringify-deterministic';
|
||||
import sortKeysRecursive from 'sort-keys-recursive';
|
||||
import { Location } from './location';
|
||||
|
||||
|
||||
@Info({title: 'HullerLocation', description: 'Smart contract for master location of hullers'})
|
||||
export class AssetTransferContract extends Contract {
|
||||
|
||||
@Transaction()
|
||||
public async Init(ctx: Context): Promise<void> {
|
||||
const assets: Location[] = [
|
||||
{
|
||||
ID: 'EVC01-VCH07C',
|
||||
Name: 'Kepala Akal',
|
||||
Latitude: '4.442027',
|
||||
Longitude: '96.750777',
|
||||
Actor: 'Huller'
|
||||
},
|
||||
];
|
||||
|
||||
for (const asset of assets) {
|
||||
// example of how to write to world state deterministically
|
||||
// use convetion of alphabetic order
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
// when retrieving data, in any lang, the order of data will be the same and consequently also the corresonding hash
|
||||
await ctx.stub.putState(asset.ID, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
console.info(`Asset ${asset.ID} initialized`);
|
||||
}
|
||||
}
|
||||
|
||||
// CreateAsset issues a new asset to the world state with given details.
|
||||
@Transaction()
|
||||
public async CreateAsset(ctx: Context, vchCode: string, name: string, latitude: string, longitude: string): Promise<void> {
|
||||
|
||||
const id = vchCode;
|
||||
const exist = await this.AssetExists(ctx, id);
|
||||
if (exist) {
|
||||
throw new Error(`The Location of VCH code ${vchCode} already exists`);
|
||||
}
|
||||
|
||||
const asset = {
|
||||
ID: id,
|
||||
Name: name,
|
||||
Latitude: latitude,
|
||||
Longitude: longitude,
|
||||
Actor: 'Huller'
|
||||
};
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
}
|
||||
|
||||
// ReadAsset returns the asset stored in the world state with given id.
|
||||
@Transaction(false)
|
||||
public async ReadAsset(ctx: Context, id: string): Promise<string> {
|
||||
const assetJSON = await ctx.stub.getState(id); // get the asset from chaincode state
|
||||
if (!assetJSON || assetJSON.length === 0) {
|
||||
throw new Error(`The location id ${id} does not exist`);
|
||||
}
|
||||
return assetJSON.toString();
|
||||
}
|
||||
|
||||
// UpdateAsset updates an existing asset in the world state with provided parameters.
|
||||
@Transaction()
|
||||
public async UpdateAsset(ctx: Context, vchCode:string, name: string, latitude: string, longitude: string): Promise<void> {
|
||||
const id = vchCode;
|
||||
const exist = await this.AssetExists(ctx, id);
|
||||
if (!exist) {
|
||||
throw new Error(`The Location of VCH code ${vchCode} does not exists`);
|
||||
}
|
||||
|
||||
// overwriting original asset with new asset
|
||||
const updatedAsset = {
|
||||
Name: name,
|
||||
Latitude: latitude,
|
||||
Longitude: longitude
|
||||
};
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
return ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(updatedAsset))));
|
||||
}
|
||||
|
||||
// DeleteAsset deletes an given asset from the world state.
|
||||
@Transaction()
|
||||
public async DeleteAsset(ctx: Context, vchCode: string): Promise<void> {
|
||||
const id = vchCode;
|
||||
const exist = await this.AssetExists(ctx, id);
|
||||
if (!exist) {
|
||||
throw new Error(`The Location of VCH code ${vchCode} does not exists`);
|
||||
}
|
||||
return ctx.stub.deleteState(id);
|
||||
}
|
||||
|
||||
// AssetExists returns true when asset with given ID exists in world state.
|
||||
@Transaction(false)
|
||||
@Returns('boolean')
|
||||
public async AssetExists(ctx: Context, id: string): Promise<boolean> {
|
||||
const assetJSON = await ctx.stub.getState(id);
|
||||
return assetJSON && assetJSON.length > 0;
|
||||
}
|
||||
|
||||
// TransferAsset updates the owner field of asset with given id in the world state, and returns the old owner.
|
||||
@Transaction()
|
||||
public async TransferAsset(ctx: Context, vchCode: string, latitude: string, longitude: string, newActor: string): Promise<string> {
|
||||
const id = vchCode;
|
||||
const assetString = await this.ReadAsset(ctx, id);
|
||||
const asset = JSON.parse(assetString);
|
||||
const oldActor = asset.Actor;
|
||||
asset.Actor = newActor;
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
return oldActor;
|
||||
}
|
||||
/*
|
||||
// TransferAsset updates the owner field of asset with given id in the world state, and returns the old owner.
|
||||
@Transaction()
|
||||
public async TransferAsset(ctx: Context, farmerId: string, newOwner: string): Promise<string> {
|
||||
const assetString = await this.ReadAsset(ctx, farmerId);
|
||||
const asset = JSON.parse(assetString);
|
||||
const oldOwner = asset.Owner;
|
||||
asset.Owner = newOwner;
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(farmerId, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
return oldOwner;
|
||||
}
|
||||
*/
|
||||
// GetAllAssets returns all assets found in the world state.
|
||||
@Transaction(false)
|
||||
@Returns('string')
|
||||
public async GetAllAssets(ctx: Context): Promise<string> {
|
||||
const allResults = [];
|
||||
// range query with empty string for startKey and endKey does an open-ended query of all assets in the chaincode namespace.
|
||||
const iterator = await ctx.stub.getStateByRange('', '');
|
||||
let result = await iterator.next();
|
||||
while (!result.done) {
|
||||
const strValue = Buffer.from(result.value.value.toString()).toString('utf8');
|
||||
let record;
|
||||
try {
|
||||
record = JSON.parse(strValue);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
record = strValue;
|
||||
}
|
||||
allResults.push(record);
|
||||
result = await iterator.next();
|
||||
}
|
||||
return JSON.stringify(allResults);
|
||||
}
|
||||
|
||||
protected randomID() {
|
||||
|
||||
let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
||||
var text = "";
|
||||
for (let i = 0; i < 100; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {AssetTransferContract} from './assetTransfer';
|
||||
|
||||
export {AssetTransferContract} from './assetTransfer';
|
||||
|
||||
export const contracts: any[] = [AssetTransferContract];
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {Object, Property} from 'fabric-contract-api';
|
||||
|
||||
@Object()
|
||||
export class Location {
|
||||
@Property()
|
||||
public ID: string;
|
||||
|
||||
@Property()
|
||||
public Name: string;
|
||||
|
||||
@Property()
|
||||
public Latitude: string;
|
||||
|
||||
@Property()
|
||||
public Longitude: string;
|
||||
|
||||
@Property()
|
||||
public Actor: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": "dist",
|
||||
"target": "es2017",
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"./src/**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"defaultSeverity": "error",
|
||||
"extends": [
|
||||
"tslint:recommended"
|
||||
],
|
||||
"jsRules": {},
|
||||
"rules": {
|
||||
"indent": [true, "spaces", 4],
|
||||
"linebreak-style": [true, "LF"],
|
||||
"quotemark": [true, "single"],
|
||||
"semicolon": [true, "always"],
|
||||
"no-console": false,
|
||||
"curly": true,
|
||||
"triple-equals": true,
|
||||
"no-string-throw": true,
|
||||
"no-var-keyword": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"object-literal-key-quotes": [true, "as-needed"],
|
||||
"object-literal-sort-keys": false,
|
||||
"max-line-length": false
|
||||
},
|
||||
"rulesDirectory": []
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
node_modules
|
||||
16
asset-transfer-basic/chaincode-typescript-huller-to-export/.gitignore
vendored
Normal file
16
asset-transfer-basic/chaincode-typescript-huller-to-export/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
package-lock.json
|
||||
|
||||
# Compiled TypeScript files
|
||||
dist
|
||||
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
FROM node:16 AS builder
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy node.js source and build, changing owner as well
|
||||
COPY --chown=node:node . /usr/src/app
|
||||
ENV npm_config_cache=/usr/src/app
|
||||
RUN npm ci && npm run package
|
||||
|
||||
|
||||
FROM node:16 AS production
|
||||
ARG CC_SERVER_PORT
|
||||
|
||||
# Setup tini to work better handle signals
|
||||
ENV TINI_VERSION v0.19.0
|
||||
ENV PLATFORM=amd64
|
||||
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-${PLATFORM} /tini
|
||||
RUN chmod +x /tini
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY --chown=node:node --from=builder /usr/src/app/dist ./dist
|
||||
COPY --chown=node:node --from=builder /usr/src/app/package.json ./
|
||||
COPY --chown=node:node --from=builder /usr/src/app/npm-shrinkwrap.json ./
|
||||
COPY --chown=node:node docker/docker-entrypoint.sh /usr/src/app/docker-entrypoint.sh
|
||||
|
||||
RUN npm ci --omit=dev && npm cache clean --force
|
||||
|
||||
ENV PORT $CC_SERVER_PORT
|
||||
EXPOSE $CC_SERVER_PORT
|
||||
ENV NODE_ENV=production
|
||||
|
||||
USER node
|
||||
ENTRYPOINT [ "/tini", "--", "/usr/src/app/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
|
||||
npm run start:server-debug
|
||||
elif [ "${CORE_PEER_TLS_ENABLED,,}" = "true" ]; then
|
||||
npm run start:server
|
||||
else
|
||||
npm run start:server-nontls
|
||||
fi
|
||||
|
||||
2946
asset-transfer-basic/chaincode-typescript-huller-to-export/npm-shrinkwrap.json
generated
Normal file
2946
asset-transfer-basic/chaincode-typescript-huller-to-export/npm-shrinkwrap.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"name": "asset-transfer-basic",
|
||||
"version": "1.0.0",
|
||||
"description": "Asset Transfer Basic contract implemented in TypeScript",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=5"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "tslint -c tslint.json 'src/**/*.ts'",
|
||||
"pretest": "npm run lint",
|
||||
"test": "nyc mocha -r ts-node/register src/**/*.spec.ts",
|
||||
"start": "set -x && fabric-chaincode-node start",
|
||||
"build": "tsc",
|
||||
"build:watch": "tsc -w",
|
||||
"prepublishOnly": "npm run build",
|
||||
"docker": "docker build -f ./Dockerfile -t asset-transfer-basic .",
|
||||
"package": "npm run build && npm shrinkwrap",
|
||||
"start:server-nontls": "set -x && fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID",
|
||||
"start:server-debug": "set -x && NODE_OPTIONS='--inspect=0.0.0.0:9229' fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID",
|
||||
"start:server": "set -x && fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID --chaincode-tls-key-file=/hyperledger/privatekey.pem --chaincode-tls-client-cacert-file=/hyperledger/rootcert.pem --chaincode-tls-cert-file=/hyperledger/cert.pem"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"author": "Hyperledger",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"fabric-contract-api": "^2.4.0",
|
||||
"fabric-shim": "^2.4.0",
|
||||
"json-stringify-deterministic": "^1.0.1",
|
||||
"sort-keys-recursive": "^2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^12.20.55",
|
||||
"@types/sinon": "^5.0.7",
|
||||
"@types/sinon-chai": "^3.2.1",
|
||||
"chai": "^4.2.0",
|
||||
"mocha": "^10.0.0",
|
||||
"nyc": "^14.1.1",
|
||||
"sinon": "^7.1.1",
|
||||
"sinon-chai": "^3.3.0",
|
||||
"ts-node": "^7.0.1",
|
||||
"tslint": "^5.11.0",
|
||||
"typescript": "^4.4"
|
||||
},
|
||||
"nyc": {
|
||||
"extension": [
|
||||
".ts",
|
||||
".tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"coverage/**",
|
||||
"dist/**"
|
||||
],
|
||||
"reporter": [
|
||||
"text-summary",
|
||||
"html"
|
||||
],
|
||||
"all": true,
|
||||
"check-coverage": true,
|
||||
"statements": 100,
|
||||
"branches": 100,
|
||||
"functions": 100,
|
||||
"lines": 100
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
// Deterministic JSON.stringify()
|
||||
import {Context, Contract, Info, Returns, Transaction} from 'fabric-contract-api';
|
||||
import stringify from 'json-stringify-deterministic';
|
||||
import sortKeysRecursive from 'sort-keys-recursive';
|
||||
import { Huller } from './huller';
|
||||
|
||||
@Info({title: 'HullerTransfer', description: 'Smart contract for trading assets'})
|
||||
export class AssetTransferContract extends Contract {
|
||||
|
||||
@Transaction()
|
||||
public async Init(ctx: Context): Promise<void> {
|
||||
const assets: Huller[] = [
|
||||
{
|
||||
ID: 'PO/EVC03-VCH07B/0823/001'+'EVC01-VCH07C-VCP01'+'1',
|
||||
PoNumber: 'PO/EVC03-VCH07B/0823/001',
|
||||
VcpCode: 'EVC01-VCH07C-VCP01',
|
||||
BatchNumber: '1',
|
||||
VchCode: 'EVC01-VCH07C',
|
||||
VchDeliveryDate: '28-Aug-2023',
|
||||
VchItemQty: '75.29',
|
||||
VchDeliveryNumber: 'ACEHVCH07C/0823/001',
|
||||
EvcItemQty: '75.29',
|
||||
EvcCode: 'EVC01',
|
||||
Lot: '1',
|
||||
ItemGrade: 'G1 Mandheling',
|
||||
CustomerPO: '1/VC/98768/TYR',
|
||||
ShippingDate: '28/01/2024',
|
||||
Port: 'Belawan',
|
||||
Actor: 'huller'
|
||||
}
|
||||
];
|
||||
|
||||
for (const asset of assets) {
|
||||
// example of how to write to world state deterministically
|
||||
// use convetion of alphabetic order
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
// when retrieving data, in any lang, the order of data will be the same and consequently also the corresonding hash
|
||||
await ctx.stub.putState(asset.ID, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
console.info(`PO Number ${asset.PoNumber} with VCP Code EVC01-VCH07C-VCP01 and Batch Number 1 initialized`);
|
||||
}
|
||||
}
|
||||
|
||||
// CreateAsset issues a new asset to the world state with given details.
|
||||
@Transaction()
|
||||
public async CreateAsset(ctx: Context, poNumber: string, vcpCode: string, batchNumber: string,
|
||||
vchCode: string, vchDeliveryDate: string, vchItemQty: string, vchDeliveryNumber: string,
|
||||
evcItemQty: string, evcCode: string, lot: string, itemGrade: string, customerPO: string,
|
||||
shippingDate: string, port: string
|
||||
): Promise<void> {
|
||||
|
||||
const id = poNumber+vcpCode+batchNumber;
|
||||
const exists = await this.AssetExists(ctx, id);
|
||||
if (exists) {
|
||||
throw new Error(`The asset ${id} already exists`);
|
||||
}
|
||||
|
||||
const asset = {
|
||||
ID: id,
|
||||
PoNumber: poNumber,
|
||||
VcpCode: vcpCode,
|
||||
BatchNumber: batchNumber,
|
||||
VchCode: vchCode,
|
||||
VchDeliveryDate: vchDeliveryDate,
|
||||
VchItemQty: vchItemQty,
|
||||
VchDeliveryNumber: vchDeliveryNumber,
|
||||
EvcItemQty: evcItemQty,
|
||||
EvcCode: evcCode,
|
||||
Lot: lot,
|
||||
ItemGrade: itemGrade,
|
||||
CustomerPO: customerPO,
|
||||
ShippingDate: shippingDate,
|
||||
Port: port,
|
||||
Actor: 'huller'
|
||||
};
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
}
|
||||
|
||||
// ReadAsset returns the asset stored in the world state with given id.
|
||||
@Transaction(false)
|
||||
public async ReadAsset(ctx: Context, id: string): Promise<string> {
|
||||
const assetJSON = await ctx.stub.getState(id); // get the asset from chaincode state
|
||||
if (!assetJSON || assetJSON.length === 0) {
|
||||
throw new Error(`The asset ${id} does not exist`);
|
||||
}
|
||||
return assetJSON.toString();
|
||||
}
|
||||
|
||||
// UpdateAsset updates an existing asset in the world state with provided parameters.
|
||||
@Transaction()
|
||||
public async UpdateAsset(ctx: Context, poNumber: string, vcpCode: string, batchNumber: string,
|
||||
vchCode: string, vchDeliveryDate: string, vchItemQty: string, vchDeliveryNumber: string,
|
||||
evcItemQty: string, evcCode: string, lot: string, itemGrade: string, customerPO: string,
|
||||
shippingDate: string, port: string): Promise<void> {
|
||||
|
||||
const id = poNumber+vcpCode+batchNumber;
|
||||
const exists = await this.AssetExists(ctx, id);
|
||||
if (!exists) {
|
||||
throw new Error(`The PO Number ${poNumber} ${vcpCode} and batch number ${batchNumber} with does not exist`);
|
||||
}
|
||||
|
||||
// overwriting original asset with new asset
|
||||
const updatedAsset = {
|
||||
PoNumber: poNumber,
|
||||
VcpCode: vcpCode,
|
||||
BatchNumber: batchNumber,
|
||||
VchCode: vchCode,
|
||||
VchDeliveryDate: vchDeliveryDate,
|
||||
VchItemQty: vchItemQty,
|
||||
VchDeliveryNumber: vchDeliveryNumber,
|
||||
EvcItemQty: evcItemQty,
|
||||
EvcCode: evcCode,
|
||||
Lot: lot,
|
||||
ItemGrade: itemGrade,
|
||||
CustomerPO: customerPO,
|
||||
ShippingDate: shippingDate,
|
||||
Port: port,
|
||||
};
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
return ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(updatedAsset))));
|
||||
}
|
||||
|
||||
// DeleteAsset deletes an given asset from the world state.
|
||||
@Transaction()
|
||||
public async DeleteAsset(ctx: Context, poNumber: string, vcpCode: string, batchNumber: string): Promise<void> {
|
||||
const id = poNumber+vcpCode+batchNumber;
|
||||
const exists = await this.AssetExists(ctx, id);
|
||||
if (!exists) {
|
||||
throw new Error(`The PO Number ${poNumber} ${vcpCode} and batch number ${batchNumber} with does not exist`);
|
||||
}
|
||||
return ctx.stub.deleteState(id);
|
||||
}
|
||||
|
||||
// AssetExists returns true when asset with given ID exists in world state.
|
||||
@Transaction(false)
|
||||
@Returns('boolean')
|
||||
public async AssetExists(ctx: Context, id: string): Promise<boolean> {
|
||||
const assetJSON = await ctx.stub.getState(id);
|
||||
return assetJSON && assetJSON.length > 0;
|
||||
}
|
||||
|
||||
// TransferAsset updates the owner field of asset with given id in the world state, and returns the old owner.
|
||||
@Transaction()
|
||||
public async TransferAsset(ctx: Context, poNumber: string, vcpCode: string, batchNumber: string, newActor: string): Promise<string> {
|
||||
const id = poNumber + vcpCode + batchNumber;
|
||||
const assetString = await this.ReadAsset(ctx, id);
|
||||
const asset = JSON.parse(assetString);
|
||||
const oldActor = asset.Actor;
|
||||
asset.Actor = newActor;
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
return oldActor;
|
||||
}
|
||||
|
||||
// GetAllAssets returns all assets found in the world state.
|
||||
@Transaction(false)
|
||||
@Returns('string')
|
||||
public async GetAllAssets(ctx: Context): Promise<string> {
|
||||
const allResults = [];
|
||||
// range query with empty string for startKey and endKey does an open-ended query of all assets in the chaincode namespace.
|
||||
const iterator = await ctx.stub.getStateByRange('', '');
|
||||
let result = await iterator.next();
|
||||
while (!result.done) {
|
||||
const strValue = Buffer.from(result.value.value.toString()).toString('utf8');
|
||||
let record;
|
||||
try {
|
||||
record = JSON.parse(strValue);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
record = strValue;
|
||||
}
|
||||
allResults.push(record);
|
||||
result = await iterator.next();
|
||||
}
|
||||
return JSON.stringify(allResults);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {Object, Property} from 'fabric-contract-api';
|
||||
|
||||
@Object()
|
||||
export class Huller {
|
||||
@Property()
|
||||
public ID: string;
|
||||
|
||||
@Property()
|
||||
public PoNumber: string;
|
||||
|
||||
@Property()
|
||||
public VcpCode: string;
|
||||
|
||||
@Property()
|
||||
public BatchNumber: string;
|
||||
|
||||
@Property()
|
||||
public VchCode: string;
|
||||
|
||||
@Property()
|
||||
public VchDeliveryDate: string;
|
||||
|
||||
@Property()
|
||||
public VchItemQty: string;
|
||||
|
||||
@Property()
|
||||
public VchDeliveryNumber: string;
|
||||
|
||||
@Property()
|
||||
public EvcItemQty: string;
|
||||
|
||||
@Property()
|
||||
public EvcCode: string;
|
||||
|
||||
@Property()
|
||||
public Lot: string;
|
||||
|
||||
@Property()
|
||||
public ItemGrade: string;
|
||||
|
||||
@Property()
|
||||
public CustomerPO: string;
|
||||
|
||||
@Property()
|
||||
public ShippingDate: string;
|
||||
|
||||
@Property()
|
||||
public Port: string;
|
||||
|
||||
@Property()
|
||||
public Actor: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {AssetTransferContract} from './assetTransfer';
|
||||
|
||||
export {AssetTransferContract} from './assetTransfer';
|
||||
|
||||
export const contracts: any[] = [AssetTransferContract];
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": "dist",
|
||||
"target": "es2017",
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"./src/**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"defaultSeverity": "error",
|
||||
"extends": [
|
||||
"tslint:recommended"
|
||||
],
|
||||
"jsRules": {},
|
||||
"rules": {
|
||||
"indent": [true, "spaces", 4],
|
||||
"linebreak-style": [true, "LF"],
|
||||
"quotemark": [true, "single"],
|
||||
"semicolon": [true, "always"],
|
||||
"no-console": false,
|
||||
"curly": true,
|
||||
"triple-equals": true,
|
||||
"no-string-throw": true,
|
||||
"no-var-keyword": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"object-literal-key-quotes": [true, "as-needed"],
|
||||
"object-literal-sort-keys": false,
|
||||
"max-line-length": false
|
||||
},
|
||||
"rulesDirectory": []
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
node_modules
|
||||
16
asset-transfer-basic/chaincode-typescript-pulper-location/.gitignore
vendored
Normal file
16
asset-transfer-basic/chaincode-typescript-pulper-location/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
package-lock.json
|
||||
|
||||
# Compiled TypeScript files
|
||||
dist
|
||||
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
FROM node:16 AS builder
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy node.js source and build, changing owner as well
|
||||
COPY --chown=node:node . /usr/src/app
|
||||
ENV npm_config_cache=/usr/src/app
|
||||
RUN npm ci && npm run package
|
||||
|
||||
|
||||
FROM node:16 AS production
|
||||
ARG CC_SERVER_PORT
|
||||
|
||||
# Setup tini to work better handle signals
|
||||
ENV TINI_VERSION v0.19.0
|
||||
ENV PLATFORM=amd64
|
||||
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-${PLATFORM} /tini
|
||||
RUN chmod +x /tini
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY --chown=node:node --from=builder /usr/src/app/dist ./dist
|
||||
COPY --chown=node:node --from=builder /usr/src/app/package.json ./
|
||||
COPY --chown=node:node --from=builder /usr/src/app/npm-shrinkwrap.json ./
|
||||
COPY --chown=node:node docker/docker-entrypoint.sh /usr/src/app/docker-entrypoint.sh
|
||||
|
||||
RUN npm ci --omit=dev && npm cache clean --force
|
||||
|
||||
ENV PORT $CC_SERVER_PORT
|
||||
EXPOSE $CC_SERVER_PORT
|
||||
ENV NODE_ENV=production
|
||||
|
||||
USER node
|
||||
ENTRYPOINT [ "/tini", "--", "/usr/src/app/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
|
||||
npm run start:server-debug
|
||||
elif [ "${CORE_PEER_TLS_ENABLED,,}" = "true" ]; then
|
||||
npm run start:server
|
||||
else
|
||||
npm run start:server-nontls
|
||||
fi
|
||||
|
||||
2973
asset-transfer-basic/chaincode-typescript-pulper-location/npm-shrinkwrap.json
generated
Normal file
2973
asset-transfer-basic/chaincode-typescript-pulper-location/npm-shrinkwrap.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"name": "asset-transfer-basic",
|
||||
"version": "1.0.0",
|
||||
"description": "Asset Transfer Basic contract implemented in TypeScript",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=5"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "tslint -c tslint.json 'src/**/*.ts'",
|
||||
"pretest": "npm run lint",
|
||||
"test": "nyc mocha -r ts-node/register src/**/*.spec.ts",
|
||||
"start": "set -x && fabric-chaincode-node start",
|
||||
"build": "tsc",
|
||||
"build:watch": "tsc -w",
|
||||
"prepublishOnly": "npm run build",
|
||||
"docker": "docker build -f ./Dockerfile -t asset-transfer-basic .",
|
||||
"package": "npm run build && npm shrinkwrap",
|
||||
"start:server-nontls": "set -x && fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID",
|
||||
"start:server-debug": "set -x && NODE_OPTIONS='--inspect=0.0.0.0:9229' fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID",
|
||||
"start:server": "set -x && fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID --chaincode-tls-key-file=/hyperledger/privatekey.pem --chaincode-tls-client-cacert-file=/hyperledger/rootcert.pem --chaincode-tls-cert-file=/hyperledger/cert.pem"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"author": "Hyperledger",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"fabric-contract-api": "^2.4.0",
|
||||
"fabric-shim": "^2.4.0",
|
||||
"json-stringify-deterministic": "^1.0.1",
|
||||
"sort-keys-recursive": "^2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^12.20.55",
|
||||
"@types/sinon": "^5.0.7",
|
||||
"@types/sinon-chai": "^3.2.1",
|
||||
"chai": "^4.2.0",
|
||||
"mocha": "^10.0.0",
|
||||
"nyc": "^14.1.1",
|
||||
"sinon": "^7.1.1",
|
||||
"sinon-chai": "^3.3.0",
|
||||
"ts-node": "^7.0.1",
|
||||
"tslint": "^5.11.0",
|
||||
"typescript": "^4.4"
|
||||
},
|
||||
"nyc": {
|
||||
"extension": [
|
||||
".ts",
|
||||
".tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"coverage/**",
|
||||
"dist/**"
|
||||
],
|
||||
"reporter": [
|
||||
"text-summary",
|
||||
"html"
|
||||
],
|
||||
"all": true,
|
||||
"check-coverage": true,
|
||||
"statements": 100,
|
||||
"branches": 100,
|
||||
"functions": 100,
|
||||
"lines": 100
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
// Deterministic JSON.stringify()
|
||||
import {Context, Contract, Info, Returns, Transaction} from 'fabric-contract-api';
|
||||
import stringify from 'json-stringify-deterministic';
|
||||
import sortKeysRecursive from 'sort-keys-recursive';
|
||||
import { Location } from './location';
|
||||
|
||||
|
||||
@Info({title: 'PulperLocation', description: 'Smart contract for master location of pulpers'})
|
||||
export class AssetTransferContract extends Contract {
|
||||
|
||||
@Transaction()
|
||||
public async Init(ctx: Context): Promise<void> {
|
||||
const assets: Location[] = [
|
||||
{
|
||||
ID: 'EVC01-VCH07C-VCP01',
|
||||
Name: 'Kepala Akal',
|
||||
Latitude: '4.4439877',
|
||||
Longitude: '96.7440467',
|
||||
Actor: 'Pulper'
|
||||
},
|
||||
];
|
||||
|
||||
for (const asset of assets) {
|
||||
// example of how to write to world state deterministically
|
||||
// use convetion of alphabetic order
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
// when retrieving data, in any lang, the order of data will be the same and consequently also the corresonding hash
|
||||
await ctx.stub.putState(asset.ID, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
console.info(`Asset ${asset.ID} initialized`);
|
||||
}
|
||||
}
|
||||
|
||||
// CreateAsset issues a new asset to the world state with given details.
|
||||
@Transaction()
|
||||
public async CreateAsset(ctx: Context, vcpCode: string, name: string, latitude: string, longitude: string): Promise<void> {
|
||||
|
||||
const id = vcpCode;
|
||||
const exist = await this.AssetExists(ctx, id);
|
||||
if (exist) {
|
||||
throw new Error(`The Location of VCP code ${vcpCode} already exists`);
|
||||
}
|
||||
|
||||
const asset = {
|
||||
ID: id,
|
||||
Name: name,
|
||||
Latitude: latitude,
|
||||
Longitude: longitude,
|
||||
Actor: 'Pulper'
|
||||
};
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
}
|
||||
|
||||
// ReadAsset returns the asset stored in the world state with given id.
|
||||
@Transaction(false)
|
||||
public async ReadAsset(ctx: Context, id: string): Promise<string> {
|
||||
const assetJSON = await ctx.stub.getState(id); // get the asset from chaincode state
|
||||
if (!assetJSON || assetJSON.length === 0) {
|
||||
throw new Error(`The location id ${id} does not exist`);
|
||||
}
|
||||
return assetJSON.toString();
|
||||
}
|
||||
|
||||
// UpdateAsset updates an existing asset in the world state with provided parameters.
|
||||
@Transaction()
|
||||
public async UpdateAsset(ctx: Context, vcpCode:string, name: string, latitude: string, longitude: string): Promise<void> {
|
||||
const id = vcpCode;
|
||||
const exist = await this.AssetExists(ctx, id);
|
||||
if (!exist) {
|
||||
throw new Error(`The Location of VCP code ${vcpCode} does not exists`);
|
||||
}
|
||||
|
||||
// overwriting original asset with new asset
|
||||
const updatedAsset = {
|
||||
Name: name,
|
||||
Latitude: latitude,
|
||||
Longitude: longitude
|
||||
};
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
return ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(updatedAsset))));
|
||||
}
|
||||
|
||||
// DeleteAsset deletes an given asset from the world state.
|
||||
@Transaction()
|
||||
public async DeleteAsset(ctx: Context, vcpCode: string): Promise<void> {
|
||||
const id = vcpCode;
|
||||
const exist = await this.AssetExists(ctx, id);
|
||||
if (!exist) {
|
||||
throw new Error(`The Location of VCP code ${vcpCode} does not exists`);
|
||||
}
|
||||
return ctx.stub.deleteState(id);
|
||||
}
|
||||
|
||||
// AssetExists returns true when asset with given ID exists in world state.
|
||||
@Transaction(false)
|
||||
@Returns('boolean')
|
||||
public async AssetExists(ctx: Context, id: string): Promise<boolean> {
|
||||
const assetJSON = await ctx.stub.getState(id);
|
||||
return assetJSON && assetJSON.length > 0;
|
||||
}
|
||||
|
||||
// TransferAsset updates the owner field of asset with given id in the world state, and returns the old owner.
|
||||
@Transaction()
|
||||
public async TransferAsset(ctx: Context, vcpCode: string, latitude: string, longitude: string, newActor: string): Promise<string> {
|
||||
const id = vcpCode;
|
||||
const assetString = await this.ReadAsset(ctx, id);
|
||||
const asset = JSON.parse(assetString);
|
||||
const oldActor = asset.Actor;
|
||||
asset.Actor = newActor;
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
return oldActor;
|
||||
}
|
||||
/*
|
||||
// TransferAsset updates the owner field of asset with given id in the world state, and returns the old owner.
|
||||
@Transaction()
|
||||
public async TransferAsset(ctx: Context, farmerId: string, newOwner: string): Promise<string> {
|
||||
const assetString = await this.ReadAsset(ctx, farmerId);
|
||||
const asset = JSON.parse(assetString);
|
||||
const oldOwner = asset.Owner;
|
||||
asset.Owner = newOwner;
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(farmerId, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
return oldOwner;
|
||||
}
|
||||
*/
|
||||
// GetAllAssets returns all assets found in the world state.
|
||||
@Transaction(false)
|
||||
@Returns('string')
|
||||
public async GetAllAssets(ctx: Context): Promise<string> {
|
||||
const allResults = [];
|
||||
// range query with empty string for startKey and endKey does an open-ended query of all assets in the chaincode namespace.
|
||||
const iterator = await ctx.stub.getStateByRange('', '');
|
||||
let result = await iterator.next();
|
||||
while (!result.done) {
|
||||
const strValue = Buffer.from(result.value.value.toString()).toString('utf8');
|
||||
let record;
|
||||
try {
|
||||
record = JSON.parse(strValue);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
record = strValue;
|
||||
}
|
||||
allResults.push(record);
|
||||
result = await iterator.next();
|
||||
}
|
||||
return JSON.stringify(allResults);
|
||||
}
|
||||
|
||||
protected randomID() {
|
||||
|
||||
let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
||||
var text = "";
|
||||
for (let i = 0; i < 100; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {AssetTransferContract} from './assetTransfer';
|
||||
|
||||
export {AssetTransferContract} from './assetTransfer';
|
||||
|
||||
export const contracts: any[] = [AssetTransferContract];
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {Object, Property} from 'fabric-contract-api';
|
||||
|
||||
@Object()
|
||||
export class Location {
|
||||
@Property()
|
||||
public ID: string;
|
||||
|
||||
@Property()
|
||||
public Name: string;
|
||||
|
||||
@Property()
|
||||
public Latitude: string;
|
||||
|
||||
@Property()
|
||||
public Longitude: string;
|
||||
|
||||
@Property()
|
||||
public Actor: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": "dist",
|
||||
"target": "es2017",
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"./src/**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"defaultSeverity": "error",
|
||||
"extends": [
|
||||
"tslint:recommended"
|
||||
],
|
||||
"jsRules": {},
|
||||
"rules": {
|
||||
"indent": [true, "spaces", 4],
|
||||
"linebreak-style": [true, "LF"],
|
||||
"quotemark": [true, "single"],
|
||||
"semicolon": [true, "always"],
|
||||
"no-console": false,
|
||||
"curly": true,
|
||||
"triple-equals": true,
|
||||
"no-string-throw": true,
|
||||
"no-var-keyword": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"object-literal-key-quotes": [true, "as-needed"],
|
||||
"object-literal-sort-keys": false,
|
||||
"max-line-length": false
|
||||
},
|
||||
"rulesDirectory": []
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
node_modules
|
||||
16
asset-transfer-basic/chaincode-typescript-pulper-to-huller/.gitignore
vendored
Normal file
16
asset-transfer-basic/chaincode-typescript-pulper-to-huller/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
package-lock.json
|
||||
|
||||
# Compiled TypeScript files
|
||||
dist
|
||||
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
FROM node:16 AS builder
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy node.js source and build, changing owner as well
|
||||
COPY --chown=node:node . /usr/src/app
|
||||
ENV npm_config_cache=/usr/src/app
|
||||
RUN npm ci && npm run package
|
||||
|
||||
|
||||
FROM node:16 AS production
|
||||
ARG CC_SERVER_PORT
|
||||
|
||||
# Setup tini to work better handle signals
|
||||
ENV TINI_VERSION v0.19.0
|
||||
ENV PLATFORM=amd64
|
||||
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-${PLATFORM} /tini
|
||||
RUN chmod +x /tini
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY --chown=node:node --from=builder /usr/src/app/dist ./dist
|
||||
COPY --chown=node:node --from=builder /usr/src/app/package.json ./
|
||||
COPY --chown=node:node --from=builder /usr/src/app/npm-shrinkwrap.json ./
|
||||
COPY --chown=node:node docker/docker-entrypoint.sh /usr/src/app/docker-entrypoint.sh
|
||||
|
||||
RUN npm ci --omit=dev && npm cache clean --force
|
||||
|
||||
ENV PORT $CC_SERVER_PORT
|
||||
EXPOSE $CC_SERVER_PORT
|
||||
ENV NODE_ENV=production
|
||||
|
||||
USER node
|
||||
ENTRYPOINT [ "/tini", "--", "/usr/src/app/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
|
||||
npm run start:server-debug
|
||||
elif [ "${CORE_PEER_TLS_ENABLED,,}" = "true" ]; then
|
||||
npm run start:server
|
||||
else
|
||||
npm run start:server-nontls
|
||||
fi
|
||||
|
||||
2973
asset-transfer-basic/chaincode-typescript-pulper-to-huller/npm-shrinkwrap.json
generated
Normal file
2973
asset-transfer-basic/chaincode-typescript-pulper-to-huller/npm-shrinkwrap.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"name": "asset-transfer-basic",
|
||||
"version": "1.0.0",
|
||||
"description": "Asset Transfer Basic contract implemented in TypeScript",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=5"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "tslint -c tslint.json 'src/**/*.ts'",
|
||||
"pretest": "npm run lint",
|
||||
"test": "nyc mocha -r ts-node/register src/**/*.spec.ts",
|
||||
"start": "set -x && fabric-chaincode-node start",
|
||||
"build": "tsc",
|
||||
"build:watch": "tsc -w",
|
||||
"prepublishOnly": "npm run build",
|
||||
"docker": "docker build -f ./Dockerfile -t asset-transfer-basic .",
|
||||
"package": "npm run build && npm shrinkwrap",
|
||||
"start:server-nontls": "set -x && fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID",
|
||||
"start:server-debug": "set -x && NODE_OPTIONS='--inspect=0.0.0.0:9229' fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID",
|
||||
"start:server": "set -x && fabric-chaincode-node server --chaincode-address=$CHAINCODE_SERVER_ADDRESS --chaincode-id=$CHAINCODE_ID --chaincode-tls-key-file=/hyperledger/privatekey.pem --chaincode-tls-client-cacert-file=/hyperledger/rootcert.pem --chaincode-tls-cert-file=/hyperledger/cert.pem"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"author": "Hyperledger",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"fabric-contract-api": "^2.4.0",
|
||||
"fabric-shim": "^2.4.0",
|
||||
"json-stringify-deterministic": "^1.0.1",
|
||||
"sort-keys-recursive": "^2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^12.20.55",
|
||||
"@types/sinon": "^5.0.7",
|
||||
"@types/sinon-chai": "^3.2.1",
|
||||
"chai": "^4.2.0",
|
||||
"mocha": "^10.0.0",
|
||||
"nyc": "^14.1.1",
|
||||
"sinon": "^7.1.1",
|
||||
"sinon-chai": "^3.3.0",
|
||||
"ts-node": "^7.0.1",
|
||||
"tslint": "^5.11.0",
|
||||
"typescript": "^4.4"
|
||||
},
|
||||
"nyc": {
|
||||
"extension": [
|
||||
".ts",
|
||||
".tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"coverage/**",
|
||||
"dist/**"
|
||||
],
|
||||
"reporter": [
|
||||
"text-summary",
|
||||
"html"
|
||||
],
|
||||
"all": true,
|
||||
"check-coverage": true,
|
||||
"statements": 100,
|
||||
"branches": 100,
|
||||
"functions": 100,
|
||||
"lines": 100
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
// Deterministic JSON.stringify()
|
||||
import {Context, Contract, Info, Returns, Transaction} from 'fabric-contract-api';
|
||||
import stringify from 'json-stringify-deterministic';
|
||||
import sortKeysRecursive from 'sort-keys-recursive';
|
||||
import {Pulper} from './pulper';
|
||||
|
||||
@Info({title: 'AssetTransfer', description: 'Smart contract for trading assets'})
|
||||
export class AssetTransferContract extends Contract {
|
||||
|
||||
@Transaction()
|
||||
public async Init(ctx: Context): Promise<void> {
|
||||
const assets: Pulper[] = [
|
||||
{
|
||||
ID: 'PO/EVC03-VCH07B/0823/001'+'EVC01-VCH07C-VCP01'+'1',
|
||||
PoNumber: 'PO/EVC03-VCH07B/0823/001',
|
||||
VcpCode: 'EVC01-VCH07C-VCP01',
|
||||
BatchNumber: '1',
|
||||
VcpFinishProcessDate: '23-Aug-2023',
|
||||
VcpItemQty: '155.69',
|
||||
VchCode: 'EVC01-VCH07C',
|
||||
VchDriedParchmentDate: "24-Aug-2023",
|
||||
VchDriedParchmentQty: "125.00",
|
||||
VchDeliveryDate: "28-Aug-2023",
|
||||
VchItemQty: "75.29",
|
||||
VchDeliveryNumber: "ACEHVCH07C/0823/001",
|
||||
Actor: 'Pulper',
|
||||
},
|
||||
];
|
||||
|
||||
for (const asset of assets) {
|
||||
// example of how to write to world state deterministically
|
||||
// use convetion of alphabetic order
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
// when retrieving data, in any lang, the order of data will be the same and consequently also the corresonding hash
|
||||
await ctx.stub.putState(asset.ID, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
console.info(`PO Number ${asset.PoNumber} initialized`);
|
||||
}
|
||||
}
|
||||
|
||||
// CreateAsset issues a new asset to the world state with given details.
|
||||
@Transaction()
|
||||
public async CreateAsset(ctx: Context, poNumber: string, vcpCode: string, batchNumber: string,
|
||||
vcpFinishProcessDate: string, vcpItemQty: string, vchCode: string, vchDriedParchmentDate: string,
|
||||
vchDriedParchmentQty: string, vchDeliveryDate: string, vchItemQty: string, vchDeliveryNumber: string
|
||||
): Promise<void> {
|
||||
|
||||
const id = poNumber+vcpCode+batchNumber;
|
||||
const exists = await this.AssetExists(ctx, id);
|
||||
if (exists) {
|
||||
throw new Error(`PO Number ${poNumber} with ${vcpCode} and batch ${batchNumber} already exists`);
|
||||
}
|
||||
|
||||
const asset = {
|
||||
ID: id,
|
||||
PoNumber: poNumber,
|
||||
VcpCode: vcpCode,
|
||||
BatchNumber: batchNumber,
|
||||
VcpFinishProcessDate: vcpFinishProcessDate,
|
||||
VcpItemQty: vcpItemQty,
|
||||
VchCode: vchCode,
|
||||
VchDriedParchmentDate: vchDriedParchmentDate,
|
||||
VchDriedParchmentQty: vchDriedParchmentQty,
|
||||
VchDeliveryDate: vchDeliveryDate,
|
||||
VchItemQty: vchItemQty,
|
||||
VchDeliveryNumber: vchDeliveryNumber,
|
||||
Actor: 'Pulper',
|
||||
};
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
}
|
||||
|
||||
// ReadAsset returns the asset stored in the world state with given id.
|
||||
@Transaction(false)
|
||||
public async ReadAsset(ctx: Context, id: string): Promise<string> {
|
||||
const assetJSON = await ctx.stub.getState(id); // get the asset from chaincode state
|
||||
if (!assetJSON || assetJSON.length === 0) {
|
||||
throw new Error(`ID ${id} does not exist`);
|
||||
}
|
||||
return assetJSON.toString();
|
||||
}
|
||||
|
||||
// UpdateAsset updates an existing asset in the world state with provided parameters.
|
||||
@Transaction()
|
||||
public async UpdateAsset(ctx: Context, poNumber: string, vcpCode: string, batchNumber: string,
|
||||
vcpFinishProcessDate: string, vcpItemQty: string, vchCode: string, vchDriedParchmentDate: string,
|
||||
vchDriedParchmentQty: string, vchDeliveryDate: string, vchItemQty: string, vchDeliveryNumber: string
|
||||
): Promise<void> {
|
||||
const id = poNumber+vcpCode+batchNumber;
|
||||
const exists = await this.AssetExists(ctx, id);
|
||||
if (!exists) {
|
||||
throw new Error(`PO Number ${poNumber} with ${vcpCode} and batch ${batchNumber} doesn't exists`);
|
||||
}
|
||||
|
||||
// overwriting original asset with new asset
|
||||
const updatedAsset = {
|
||||
PoNumber: poNumber,
|
||||
VcpCode: vcpCode,
|
||||
BatchNumber: batchNumber,
|
||||
VcpFinishProcessDate: vcpFinishProcessDate,
|
||||
VcpItemQty: vcpItemQty,
|
||||
VchCode: vchCode,
|
||||
VchDriedParchmentDate: vchDriedParchmentDate,
|
||||
VchDriedParchmentQty: vchDriedParchmentQty,
|
||||
VchDeliveryDate: vchDeliveryDate,
|
||||
VchItemQty: vchItemQty,
|
||||
VchDeliveryNumber: vchDeliveryNumber,
|
||||
};
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
return ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(updatedAsset))));
|
||||
}
|
||||
|
||||
// DeleteAsset deletes an given asset from the world state.
|
||||
@Transaction()
|
||||
public async DeleteAsset(ctx: Context, poNumber: string, vcpCode: string, batchNumber: string): Promise<void> {
|
||||
const id = poNumber+vcpCode+batchNumber;
|
||||
const exists = await this.AssetExists(ctx, id);
|
||||
if (!exists) {
|
||||
throw new Error(`PO Number ${poNumber} with ${vcpCode} and batch ${batchNumber} doesn't exists`);
|
||||
}
|
||||
return ctx.stub.deleteState(id);
|
||||
}
|
||||
|
||||
// AssetExists returns true when asset with given ID exists in world state.
|
||||
@Transaction(false)
|
||||
public async AssetExists(ctx: Context, id: string): Promise<boolean> {
|
||||
const assetJSON = await ctx.stub.getState(id);
|
||||
return assetJSON && assetJSON.length > 0;
|
||||
}
|
||||
|
||||
// TransferAsset updates the owner field of asset with given id in the world state, and returns the old owner.
|
||||
@Transaction()
|
||||
public async TransferAsset(ctx: Context, poNumber: string, vcpCode: string, batchNumber: string, newActor: string): Promise<string> {
|
||||
const id = poNumber+vcpCode+batchNumber;
|
||||
const assetString = await this.ReadAsset(ctx, id);
|
||||
const asset = JSON.parse(assetString);
|
||||
const oldActor = asset.Actor;
|
||||
asset.Actor = newActor;
|
||||
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
|
||||
await ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset))));
|
||||
return oldActor;
|
||||
}
|
||||
|
||||
// GetAllAssets returns all assets found in the world state.
|
||||
@Transaction(false)
|
||||
@Returns('string')
|
||||
public async GetAllAssets(ctx: Context): Promise<string> {
|
||||
const allResults = [];
|
||||
// range query with empty string for startKey and endKey does an open-ended query of all assets in the chaincode namespace.
|
||||
const iterator = await ctx.stub.getStateByRange('', '');
|
||||
let result = await iterator.next();
|
||||
while (!result.done) {
|
||||
const strValue = Buffer.from(result.value.value.toString()).toString('utf8');
|
||||
let record;
|
||||
try {
|
||||
record = JSON.parse(strValue);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
record = strValue;
|
||||
}
|
||||
allResults.push(record);
|
||||
result = await iterator.next();
|
||||
}
|
||||
return JSON.stringify(allResults);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {AssetTransferContract} from './assetTransfer';
|
||||
|
||||
export {AssetTransferContract} from './assetTransfer';
|
||||
|
||||
export const contracts: any[] = [AssetTransferContract];
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {Object, Property} from 'fabric-contract-api';
|
||||
|
||||
@Object()
|
||||
export class Pulper {
|
||||
|
||||
@Property()
|
||||
public ID: string;
|
||||
|
||||
@Property()
|
||||
public PoNumber: string;
|
||||
|
||||
@Property()
|
||||
public VcpCode: string;
|
||||
|
||||
@Property()
|
||||
public BatchNumber: string;
|
||||
|
||||
@Property()
|
||||
public VcpFinishProcessDate: string;
|
||||
|
||||
@Property()
|
||||
public VcpItemQty: string;
|
||||
|
||||
@Property()
|
||||
public VchCode: string;
|
||||
|
||||
@Property()
|
||||
public VchDriedParchmentDate: string;
|
||||
|
||||
@Property()
|
||||
public VchDriedParchmentQty: string;
|
||||
|
||||
@Property()
|
||||
public VchDeliveryDate: string;
|
||||
|
||||
@Property()
|
||||
public VchItemQty: string;
|
||||
|
||||
@Property()
|
||||
public VchDeliveryNumber: string;
|
||||
|
||||
@Property()
|
||||
public VcpProcessType?: string;
|
||||
|
||||
@Property()
|
||||
public VcpItemType?: string;
|
||||
|
||||
@Property()
|
||||
public VcpStartProcessDate?: string;
|
||||
|
||||
@Property()
|
||||
public VcpDeliveryNote?: string;
|
||||
|
||||
@Property()
|
||||
public VcpLocation?: string;
|
||||
|
||||
@Property()
|
||||
public Actor: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": "dist",
|
||||
"target": "es2017",
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"./src/**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"defaultSeverity": "error",
|
||||
"extends": [
|
||||
"tslint:recommended"
|
||||
],
|
||||
"jsRules": {},
|
||||
"rules": {
|
||||
"indent": [true, "spaces", 4],
|
||||
"linebreak-style": [true, "LF"],
|
||||
"quotemark": [true, "single"],
|
||||
"semicolon": [true, "always"],
|
||||
"no-console": false,
|
||||
"curly": true,
|
||||
"triple-equals": true,
|
||||
"no-string-throw": true,
|
||||
"no-var-keyword": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"object-literal-key-quotes": [true, "as-needed"],
|
||||
"object-literal-sort-keys": false,
|
||||
"max-line-length": false
|
||||
},
|
||||
"rulesDirectory": []
|
||||
}
|
||||
|
|
@ -210,14 +210,14 @@ Application: &ApplicationDefaults
|
|||
# Type: ImplicitMeta
|
||||
# Rule: "MAJORITY Endorsement"
|
||||
Type: Signature
|
||||
Rule: "OutOf(2, 'FarmerMSP.peer', 'PulperMSP.peer', 'HullerMSP.peer', 'ExportMSP.peer')"
|
||||
# Rule: "OR('FarmerMSP.peer', 'PulperMSP.peer', 'HullerMSP.peer', 'ExportMSP.peer')"
|
||||
# Rule: "OutOf(2, 'FarmerMSP.peer', 'PulperMSP.peer', 'HullerMSP.peer', 'ExportMSP.peer')"
|
||||
Rule: "OR('FarmerMSP.peer', 'PulperMSP.peer', 'HullerMSP.peer', 'ExportMSP.peer')"
|
||||
Endorsement:
|
||||
# Type: ImplicitMeta
|
||||
# Rule: "MAJORITY Endorsement"
|
||||
Type: Signature
|
||||
Rule: "OutOf(2, 'FarmerMSP.peer', 'PulperMSP.peer', 'HullerMSP.peer', 'ExportMSP.peer')"
|
||||
# Rule: "OR('FarmerMSP.peer', 'PulperMSP.peer', 'HullerMSP.peer', 'ExportMSP.peer')"
|
||||
# Rule: "OutOf(2, 'FarmerMSP.peer', 'PulperMSP.peer', 'HullerMSP.peer', 'ExportMSP.peer')"
|
||||
Rule: "OR('FarmerMSP.peer', 'PulperMSP.peer', 'HullerMSP.peer', 'ExportMSP.peer')"
|
||||
Capabilities:
|
||||
<<: *ApplicationCapabilities
|
||||
################################################################################
|
||||
|
|
|
|||
Loading…
Reference in a new issue