mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-23 18:15:10 +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
|
# Type: ImplicitMeta
|
||||||
# Rule: "MAJORITY Endorsement"
|
# Rule: "MAJORITY Endorsement"
|
||||||
Type: Signature
|
Type: Signature
|
||||||
Rule: "OutOf(2, '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')"
|
Rule: "OR('FarmerMSP.peer', 'PulperMSP.peer', 'HullerMSP.peer', 'ExportMSP.peer')"
|
||||||
Endorsement:
|
Endorsement:
|
||||||
# Type: ImplicitMeta
|
# Type: ImplicitMeta
|
||||||
# Rule: "MAJORITY Endorsement"
|
# Rule: "MAJORITY Endorsement"
|
||||||
Type: Signature
|
Type: Signature
|
||||||
Rule: "OutOf(2, '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')"
|
Rule: "OR('FarmerMSP.peer', 'PulperMSP.peer', 'HullerMSP.peer', 'ExportMSP.peer')"
|
||||||
Capabilities:
|
Capabilities:
|
||||||
<<: *ApplicationCapabilities
|
<<: *ApplicationCapabilities
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue