Chaincodes update

Eight chaincode & change Endorsement setting
This commit is contained in:
Faisal Abdillah 2024-06-24 13:14:27 +07:00
parent f697550d02
commit 456cf315ba
100 changed files with 30272 additions and 4 deletions

View file

@ -0,0 +1 @@
node_modules

View 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

View 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" ]

View 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

File diff suppressed because it is too large Load diff

View 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
}
}

View file

@ -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);
}
}

View 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;
}

View file

@ -0,0 +1,9 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
import {AssetTransferContract} from './assetTransfer';
export {AssetTransferContract} from './assetTransfer';
export const contracts: any[] = [AssetTransferContract];

View 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"
]
}

View 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": []
}

View file

@ -0,0 +1 @@
node_modules

View 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

View 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" ]

View 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

File diff suppressed because it is too large Load diff

View 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
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,9 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
import {AssetTransferContract} from './assetTransfer';
export {AssetTransferContract} from './assetTransfer';
export const contracts: any[] = [AssetTransferContract];

View file

@ -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;
}

View 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"
]
}

View 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": []
}

View file

@ -0,0 +1 @@
node_modules

View 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

View 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" ]

View 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

File diff suppressed because it is too large Load diff

View 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
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,9 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
import {AssetTransferContract} from './assetTransfer';
export {AssetTransferContract} from './assetTransfer';
export const contracts: any[] = [AssetTransferContract];

View file

@ -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;
}

View 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"
]
}

View 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": []
}

View file

@ -0,0 +1 @@
node_modules

View 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

View 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" ]

View 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

File diff suppressed because it is too large Load diff

View 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
}
}

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -0,0 +1,9 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
import {AssetTransferContract} from './assetTransfer';
export {AssetTransferContract} from './assetTransfer';
export const contracts: any[] = [AssetTransferContract];

View 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"
]
}

View 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": []
}

View file

@ -0,0 +1 @@
node_modules

View 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

View 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" ]

View 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

File diff suppressed because it is too large Load diff

View 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
}
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -0,0 +1,9 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
import {AssetTransferContract} from './assetTransfer';
export {AssetTransferContract} from './assetTransfer';
export const contracts: any[] = [AssetTransferContract];

View 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"
]
}

View 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": []
}

View file

@ -0,0 +1 @@
node_modules

View 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

View 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" ]

View 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

File diff suppressed because it is too large Load diff

View 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
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,9 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
import {AssetTransferContract} from './assetTransfer';
export {AssetTransferContract} from './assetTransfer';
export const contracts: any[] = [AssetTransferContract];

View file

@ -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;
}

View 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"
]
}

View 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": []
}

View file

@ -0,0 +1 @@
node_modules

View 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

View 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" ]

View 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

File diff suppressed because it is too large Load diff

View 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
}
}

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -0,0 +1,9 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
import {AssetTransferContract} from './assetTransfer';
export {AssetTransferContract} from './assetTransfer';
export const contracts: any[] = [AssetTransferContract];

View 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"
]
}

View 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": []
}

View file

@ -0,0 +1 @@
node_modules

View 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

View 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" ]

View 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

File diff suppressed because it is too large Load diff

View 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
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,9 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
import {AssetTransferContract} from './assetTransfer';
export {AssetTransferContract} from './assetTransfer';
export const contracts: any[] = [AssetTransferContract];

View file

@ -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;
}

View 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"
]
}

View 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": []
}

View file

@ -0,0 +1 @@
node_modules

View 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

View 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" ]

View 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

File diff suppressed because it is too large Load diff

View 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
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,9 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
import {AssetTransferContract} from './assetTransfer';
export {AssetTransferContract} from './assetTransfer';
export const contracts: any[] = [AssetTransferContract];

View file

@ -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;
}

View 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"
]
}

View 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": []
}

View file

@ -210,14 +210,14 @@ Application: &ApplicationDefaults
# Type: ImplicitMeta
# Rule: "MAJORITY Endorsement"
Type: Signature
Rule: "OutOf(2, 'FarmerMSP.peer', 'PulperMSP.peer', 'HullerMSP.peer', 'ExportMSP.peer')"
# Rule: "OR('FarmerMSP.peer', 'PulperMSP.peer', 'HullerMSP.peer', 'ExportMSP.peer')"
# Rule: "OutOf(2, 'FarmerMSP.peer', 'PulperMSP.peer', 'HullerMSP.peer', 'ExportMSP.peer')"
Rule: "OR('FarmerMSP.peer', 'PulperMSP.peer', 'HullerMSP.peer', 'ExportMSP.peer')"
Endorsement:
# Type: ImplicitMeta
# Rule: "MAJORITY Endorsement"
Type: Signature
Rule: "OutOf(2, 'FarmerMSP.peer', 'PulperMSP.peer', 'HullerMSP.peer', 'ExportMSP.peer')"
# Rule: "OR('FarmerMSP.peer', 'PulperMSP.peer', 'HullerMSP.peer', 'ExportMSP.peer')"
# Rule: "OutOf(2, 'FarmerMSP.peer', 'PulperMSP.peer', 'HullerMSP.peer', 'ExportMSP.peer')"
Rule: "OR('FarmerMSP.peer', 'PulperMSP.peer', 'HullerMSP.peer', 'ExportMSP.peer')"
Capabilities:
<<: *ApplicationCapabilities
################################################################################