Update Node chaincode for v2.5.5 release

Signed-off-by: Mark S. Lewis <Mark.S.Lewis@outlook.com>
This commit is contained in:
Mark S. Lewis 2024-05-31 19:22:30 +01:00 committed by Dave Enyeart
parent 3c63eac4e3
commit 0ed34585e1
33 changed files with 15507 additions and 10269 deletions

File diff suppressed because it is too large Load diff

View file

@ -4,8 +4,7 @@
"description": "Asset-Transfer-Basic contract implemented in JavaScript",
"main": "index.js",
"engines": {
"node": ">=12",
"npm": ">=5"
"node": ">=18"
},
"scripts": {
"lint": "eslint *.js */**.js",
@ -17,18 +16,18 @@
"author": "Hyperledger",
"license": "Apache-2.0",
"dependencies": {
"fabric-contract-api": "^2.0.0",
"fabric-shim": "^2.0.0",
"json-stringify-deterministic": "^1.0.1",
"sort-keys-recursive": "^2.1.2"
"fabric-contract-api": "~2.5.5",
"fabric-shim": "~2.5.5",
"json-stringify-deterministic": "^1.0.0",
"sort-keys-recursive": "^2.1.0"
},
"devDependencies": {
"chai": "^4.1.2",
"eslint": "^4.19.1",
"mocha": "^8.0.1",
"nyc": "^14.1.1",
"sinon": "^6.0.0",
"sinon-chai": "^3.2.0"
"chai": "^4.4.1",
"eslint": "^8.57.0",
"mocha": "^10.4.0",
"nyc": "^15.1.0",
"sinon": "^18.0.0",
"sinon-chai": "^3.7.0"
},
"nyc": {
"exclude": [

View file

@ -227,10 +227,10 @@ describe('Asset Transfer Basic Tests', () => {
expect(ret.length).to.equal(4);
let expected = [
{Record: {ID: 'asset1', Color: 'blue', Size: 5, Owner: 'Robert', AppraisedValue: 100}},
{Record: {ID: 'asset2', Color: 'orange', Size: 10, Owner: 'Paul', AppraisedValue: 200}},
{Record: {ID: 'asset3', Color: 'red', Size: 15, Owner: 'Troy', AppraisedValue: 300}},
{Record: {ID: 'asset4', Color: 'pink', Size: 20, Owner: 'Van', AppraisedValue: 400}}
{ID: 'asset1', Color: 'blue', Size: 5, Owner: 'Robert', AppraisedValue: 100},
{ID: 'asset2', Color: 'orange', Size: 10, Owner: 'Paul', AppraisedValue: 200},
{ID: 'asset3', Color: 'red', Size: 15, Owner: 'Troy', AppraisedValue: 300},
{ID: 'asset4', Color: 'pink', Size: 20, Owner: 'Van', AppraisedValue: 400}
];
expect(ret).to.eql(expected);
@ -256,10 +256,10 @@ describe('Asset Transfer Basic Tests', () => {
expect(ret.length).to.equal(4);
let expected = [
{Record: 'non-json-value'},
{Record: {ID: 'asset2', Color: 'orange', Size: 10, Owner: 'Paul', AppraisedValue: 200}},
{Record: {ID: 'asset3', Color: 'red', Size: 15, Owner: 'Troy', AppraisedValue: 300}},
{Record: {ID: 'asset4', Color: 'pink', Size: 20, Owner: 'Van', AppraisedValue: 400}}
'non-json-value',
{ID: 'asset2', Color: 'orange', Size: 10, Owner: 'Paul', AppraisedValue: 200},
{ID: 'asset3', Color: 'red', Size: 15, Owner: 'Troy', AppraisedValue: 300},
{ID: 'asset4', Color: 'pink', Size: 20, Owner: 'Van', AppraisedValue: 400}
];
expect(ret).to.eql(expected);

View file

@ -0,0 +1,13 @@
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked, {
languageOptions: {
ecmaVersion: 2023,
sourceType: 'module',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: import.meta.dirname,
},
},
});

File diff suppressed because it is too large Load diff

View file

@ -5,13 +5,12 @@
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"engines": {
"node": ">=12",
"npm": ">=5"
"node": ">=18"
},
"scripts": {
"lint": "tslint -c tslint.json 'src/**/*.ts'",
"lint": "eslint src",
"pretest": "npm run lint",
"test": "nyc mocha -r ts-node/register src/**/*.spec.ts",
"test": "",
"start": "set -x && fabric-chaincode-node start",
"build": "tsc",
"build:watch": "tsc -w",
@ -26,44 +25,17 @@
"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"
"fabric-contract-api": "~2.5.5",
"fabric-shim": "~2.5.5",
"json-stringify-deterministic": "^1.0.0",
"sort-keys-recursive": "^2.1.0"
},
"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
"@types/node": "^18.19.33",
"@eslint/js": "^9.3.0",
"@tsconfig/node18": "^18.2.4",
"eslint": "^8.57.0",
"typescript": "~5.4.5",
"typescript-eslint": "^7.11.0"
}
}

View file

@ -91,7 +91,7 @@ export class AssetTransferContract extends Contract {
@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) {
if (assetJSON.length === 0) {
throw new Error(`The asset ${id} does not exist`);
}
return assetJSON.toString();
@ -132,14 +132,14 @@ export class AssetTransferContract extends Contract {
@Returns('boolean')
public async AssetExists(ctx: Context, id: string): Promise<boolean> {
const assetJSON = await ctx.stub.getState(id);
return assetJSON && assetJSON.length > 0;
return 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, id: string, newOwner: string): Promise<string> {
const assetString = await this.ReadAsset(ctx, id);
const asset = JSON.parse(assetString);
const asset = JSON.parse(assetString) as Asset;
const oldOwner = asset.Owner;
asset.Owner = newOwner;
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
@ -159,7 +159,7 @@ export class AssetTransferContract extends Contract {
const strValue = Buffer.from(result.value.value.toString()).toString('utf8');
let record;
try {
record = JSON.parse(strValue);
record = JSON.parse(strValue) as Asset;
} catch (err) {
console.log(err);
record = strValue;

View file

@ -2,8 +2,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {type Contract} from 'fabric-contract-api';
import {AssetTransferContract} from './assetTransfer';
export {AssetTransferContract} from './assetTransfer';
export const contracts: any[] = [AssetTransferContract];
export const contracts: typeof Contract[] = [AssetTransferContract];

View file

@ -1,19 +1,17 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/node18/tsconfig.json",
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"outDir": "dist",
"target": "es2017",
"moduleResolution": "node",
"module": "commonjs",
"esModuleInterop": true,
"declaration": true,
"sourceMap": true
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"strict": true,
"noUnusedLocals": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"./src/**/*"
],
"exclude": [
"./src/**/*.spec.ts"
]
"include": ["src/"]
}

View file

@ -1,23 +0,0 @@
{
"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": []
}

File diff suppressed because it is too large Load diff

View file

@ -4,8 +4,7 @@
"description": "Asset-Transfer-Events contract implemented in JavaScript",
"main": "index.js",
"engines": {
"node": ">=12",
"npm": ">=5"
"node": ">=18"
},
"scripts": {
"lint": "eslint .",
@ -17,16 +16,16 @@
"author": "Hyperledger",
"license": "Apache-2.0",
"dependencies": {
"fabric-contract-api": "^2.0.0",
"fabric-shim": "^2.0.0"
"fabric-contract-api": "~2.5.5",
"fabric-shim": "~2.5.5"
},
"devDependencies": {
"chai": "^4.1.2",
"eslint": "^4.19.1",
"mocha": "^8.0.1",
"nyc": "^14.1.1",
"sinon": "^6.0.0",
"sinon-chai": "^3.2.0"
"chai": "^4.4.1",
"eslint": "^8.57.0",
"mocha": "^10.4.0",
"nyc": "^15.1.0",
"sinon": "^18.0.0",
"sinon-chai": "^3.7.0"
},
"nyc": {
"exclude": [

View file

@ -84,9 +84,8 @@ describe('Asset Transfer Events Tests', () => {
Price: '90',
salt: Buffer.from(randomNumber.toString()).toString('hex')
};
transientMap = {
asset_properties: Buffer.from(JSON.stringify(asset_properties))
};
transientMap = new Map();
transientMap.set('asset_properties', Buffer.from(JSON.stringify(asset_properties)));
});
describe('Test CreateAsset', () => {

File diff suppressed because it is too large Load diff

View file

@ -4,20 +4,20 @@
"description": "asset chaincode implemented in node.js",
"main": "index.js",
"engines": {
"node": ">=12",
"npm": ">=5.3.0"
"node": ">=18"
},
"scripts": {
"start": "fabric-chaincode-node start",
"lint": "eslint *.js */**.js"
"lint": "eslint *.js */**.js",
"test": ""
},
"engine-strict": true,
"license": "Apache-2.0",
"dependencies": {
"fabric-contract-api": "^2.0.0",
"fabric-shim": "^2.0.0"
"fabric-contract-api": "~2.5.5",
"fabric-shim": "~2.5.5"
},
"devDependencies": {
"eslint": "^7.32.0"
"eslint": "^8.57.0"
}
}

View file

@ -0,0 +1,13 @@
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked, {
languageOptions: {
ecmaVersion: 2023,
sourceType: 'module',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: import.meta.dirname,
},
},
});

File diff suppressed because it is too large Load diff

View file

@ -5,13 +5,12 @@
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"engines": {
"node": ">=12",
"npm": ">=5"
"node": ">=18"
},
"scripts": {
"lint": "tslint -c tslint.json 'src/**/*.ts'",
"lint": "eslint src",
"pretest": "npm run lint",
"test": "nyc mocha -r ts-node/register src/**/*.spec.ts",
"test": "",
"start": "set -x && fabric-chaincode-node start",
"build": "tsc",
"build:watch": "tsc -w",
@ -26,44 +25,17 @@
"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"
"fabric-contract-api": "~2.5.5",
"fabric-shim": "~2.5.5",
"json-stringify-deterministic": "^1.0.0",
"sort-keys-recursive": "^2.1.0"
},
"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": "^3.1.6"
},
"nyc": {
"extension": [
".ts",
".tsx"
],
"exclude": [
"coverage/**",
"dist/**"
],
"reporter": [
"text-summary",
"html"
],
"all": true,
"check-coverage": true,
"statements": 100,
"branches": 100,
"functions": 100,
"lines": 100
"@types/node": "^18.19.33",
"@eslint/js": "^9.3.0",
"@tsconfig/node18": "^18.2.4",
"eslint": "^8.57.0",
"typescript": "~5.4.5",
"typescript-eslint": "^7.11.0"
}
}

View file

@ -2,23 +2,41 @@
SPDX-License-Identifier: Apache-2.0
*/
import { Object, Property } from 'fabric-contract-api';
import { Object, Property } from "fabric-contract-api";
import { nonEmptyString, positiveNumber } from "./utils";
@Object()
// Asset describes main asset details that are visible to all organizations
export class Asset {
@Property()
public docType?: string;
docType?: string;
@Property()
public ID: string;
ID: string = "";
@Property()
public Color: string;
Color: string = "";
@Property()
public Size: number;
Size: number = 0;
@Property()
public Owner: string;
Owner: string = "";
static fromBytes(bytes: Uint8Array): Asset {
if (bytes.length === 0) {
throw new Error("no asset data");
}
const json = Buffer.from(bytes).toString();
const properties = JSON.parse(json) as Partial<Asset>;
const result = new Asset();
result.docType = properties.docType;
result.ID = nonEmptyString(properties.ID, "ID field must be a non-empty string");
result.Color = nonEmptyString(properties.Color, "Color field must be a non-empty string");
result.Size = positiveNumber(properties.Size, "Size field must be a positive integer");
result.Owner = nonEmptyString(properties.Owner, "appraiseOwner field must be a non-empty string");
return result;
}
}

View file

@ -6,7 +6,7 @@ import stringify from 'json-stringify-deterministic';
import sortKeysRecursive from 'sort-keys-recursive';
import { Asset } from './asset';
import { AssetPrivateDetails } from './assetTransferDetails';
import { AssetTransferTransientInput } from './assetTransferTransientInput';
import { TransientAssetDelete, TransientAssetOwner, TransientAssetProperties, TransientAssetPurge, TransientAssetValue } from './assetTransferTransientInput';
import { TransferAgreement } from './transferAgreement';
const assetCollection = 'assetCollection';
@ -19,35 +19,12 @@ export class AssetTransfer extends Contract {
@Transaction()
public async CreateAsset(ctx: Context): Promise<void> {
const transientMap = ctx.stub.getTransient();
const transientAssetJSON = transientMap.get('asset_properties');
if (transientAssetJSON.length === 0) {
throw new Error('asset properties not found in the transient map');
}
const jsonBytesToString = String.fromCharCode(...transientAssetJSON);
const jsonFromString = JSON.parse(jsonBytesToString);
// Check properties
if (jsonFromString.objectType.length === 0) {
throw new Error('objectType field must be a non-empty string');
}
if (jsonFromString.assetID.length === 0) {
throw new Error('assetID field must be a non-empty string');
}
if (jsonFromString.color.length === 0) {
throw new Error('color field must be a non-empty string');
}
if (jsonFromString.size <= 0) {
throw new Error('size field must be a positive integer');
}
if (jsonFromString.appraisedValue <= 0) {
throw new Error('appraisedValue field must be a positive integer');
}
const assetProperties = new TransientAssetProperties(transientMap);
// Check if asset already exists
const assetAsBytes = await ctx.stub.getPrivateData(assetCollection, jsonFromString.assetID);
const assetAsBytes = await ctx.stub.getPrivateData(assetCollection, assetProperties.assetID);
if (assetAsBytes.length !== 0) {
throw new Error('this asset already exists: ' + jsonFromString.assetID);
throw new Error('this asset already exists: ' + assetProperties.assetID);
}
// Get ID of submitting client identity
@ -56,14 +33,12 @@ export class AssetTransfer extends Contract {
// Verify that the client is submitting request to peer in their organization
// This is to ensure that a client from another org doesn't attempt to read or
// write private data from this peer.
const err = await this.verifyClientOrgMatchesPeerOrg(ctx);
if (err !== null) {
throw new Error('CreateAsset cannot be performed: Error ' + err);
}
this.verifyClientOrgMatchesPeerOrg(ctx);
const asset: Asset = {
ID: jsonFromString.assetID,
Color: jsonFromString.color,
Size: jsonFromString.size,
ID: assetProperties.assetID,
Color: assetProperties.color,
Size: assetProperties.size,
Owner: clientID,
};
@ -74,16 +49,16 @@ export class AssetTransfer extends Contract {
// Save asset details to collection visible to owning organization
const assetPrivateDetails: AssetPrivateDetails = {
ID: jsonFromString.assetID,
AppraisedValue: jsonFromString.appraisedValue,
ID: assetProperties.assetID,
AppraisedValue: assetProperties.appraisedValue,
};
// Get collection name for this organization.
const orgCollection = await this.getCollectionName(ctx);
const orgCollection = this.getCollectionName(ctx);
// Put asset appraised value into owners org specific private data collection
console.log('Put: collection %v, ID %v', orgCollection, jsonFromString.assetID);
console.log('Put: collection %v, ID %v', orgCollection, assetProperties.assetID);
await ctx.stub.putPrivateData(orgCollection, asset.ID, Buffer.from(stringify(sortKeysRecursive(assetPrivateDetails))));
return null;
}
// AgreeToTransfer is used by the potential buyer of the asset to agree to the
// asset value. The agreed to appraisal value is stored in the buying orgs
// org specifc collection, while the the buyer client ID is stored in the asset collection
@ -94,33 +69,19 @@ export class AssetTransfer extends Contract {
const clientID = ctx.clientIdentity.getID();
// Value is private, therefore it gets passed in transient field
const transientMap = ctx.stub.getTransient();
// Persist the JSON bytes as-is so that there is no risk of nondeterministic marshaling.
const valueJSONasBytes = transientMap.get('asset_value');
if (valueJSONasBytes.length === 0) {
throw new Error('asset value not found in the transient map');
}
const jsonBytesToString = String.fromCharCode(...valueJSONasBytes);
const jsonFromString = JSON.parse(jsonBytesToString);
// Do some error checking since we get the chance
if (jsonFromString.assetID.length === 0) {
throw new Error('assetID field must be a non-empty string');
}
if (jsonFromString.appraisedValue <= 0) {
throw new Error('appraisedValue field must be a positive integer');
}
const assetValue = new TransientAssetValue(transientMap);
const valueJSON: AssetPrivateDetails = {
ID: jsonFromString.assetID,
AppraisedValue: jsonFromString.appraisedValue,
ID: assetValue.assetID,
AppraisedValue: assetValue.appraisedValue,
};
// Read asset from the private data collection
const asset = await this.ReadAsset(ctx, valueJSON.ID);
// Verify that the client is submitting request to peer in their organization
const err = await this.verifyClientOrgMatchesPeerOrg(ctx);
if (err !== null) {
throw new Error('AgreeToTransfer cannot be performed: Error ' + err);
}
this.verifyClientOrgMatchesPeerOrg(ctx);
// Get collection name for this organization. Needs to be read by a member of the organization.
const orgCollection = await this.getCollectionName(ctx);
const orgCollection = this.getCollectionName(ctx);
console.log(`AgreeToTransfer Put: collection ${orgCollection}, ID ${valueJSON.ID}`);
// Put agreed value in the org specifc private data collection
await ctx.stub.putPrivateData(orgCollection, asset.ID, Buffer.from(stringify(sortKeysRecursive(valueJSON))));
@ -130,170 +91,123 @@ export class AssetTransfer extends Contract {
const transferAgreeKey = ctx.stub.createCompositeKey(transferAgreementObjectType, [valueJSON.ID]);
console.log(`AgreeToTransfer Put: collection ${assetCollection}, ID ${valueJSON.ID}, Key ${transferAgreeKey}`);
await ctx.stub.putPrivateData(assetCollection, transferAgreeKey, new Uint8Array(Buffer.from(clientID)));
return null;
}
@Transaction()
// TransferAsset transfers the asset to the new owner by setting a new owner ID
public async TransferAsset(ctx: Context): Promise<void> {
const transientMap = ctx.stub.getTransient();
// Asset properties are private, therefore they get passed in transient field
const transientTransferJSON = transientMap.get('asset_owner');
if (transientTransferJSON.length === 0) {
throw new Error('asset owner not found in the transient map');
}
const jsonBytesToString = String.fromCharCode(...transientTransferJSON);
const jsonFromString = JSON.parse(jsonBytesToString);
// Do some error checking since we get the chance
if (jsonFromString.assetID.length === 0) {
throw new Error('assetID field must be a non-empty string');
}
if (jsonFromString.buyerMSP.length === 0) {
throw new Error('buyerMSP field must be a non-empty string');
}
const assetTransferInput: AssetTransferTransientInput = {
ID: jsonFromString.assetID,
BuyerMSP: jsonFromString.buyerMSP,
};
console.log('TransferAsset: verify asset exists ID ' + assetTransferInput.ID);
// Read asset from the private data collection
const asset = await this.ReadAsset(ctx, assetTransferInput.ID);
// Verify that the client is submitting request to peer in their organization
const err = await this.verifyClientOrgMatchesPeerOrg(ctx);
if (err !== null) {
throw new Error('TransferAsset cannot be performed: Error ' + err);
}
// Verify transfer details and transfer owner
await this.verifyAgreement(ctx, assetTransferInput.ID, asset.Owner, assetTransferInput.BuyerMSP);
const transientMap = ctx.stub.getTransient();
const assetOwner = new TransientAssetOwner(transientMap);
const transferAgreement = await this.ReadTransferAgreement(ctx, assetTransferInput.ID);
console.log('TransferAsset: verify asset exists ID ' + assetOwner.assetID);
// Read asset from the private data collection
const asset = await this.ReadAsset(ctx, assetOwner.assetID);
// Verify that the client is submitting request to peer in their organization
this.verifyClientOrgMatchesPeerOrg(ctx);
// Verify transfer details and transfer owner
await this.verifyAgreement(ctx, assetOwner.assetID, asset.Owner, assetOwner.buyerMSP);
const transferAgreement = await this.ReadTransferAgreement(ctx, assetOwner.assetID);
if (transferAgreement.BuyerID === '') {
throw new Error('BuyerID not found in TransferAgreement for ' + assetTransferInput.ID);
throw new Error('BuyerID not found in TransferAgreement for ' + assetOwner.assetID);
}
// Transfer asset in private data collection to new owner
asset.Owner = transferAgreement.BuyerID;
console.log(`TransferAsset Put: collection ${assetCollection}, ID ${assetTransferInput.ID}`);
await ctx.stub.putPrivateData(assetCollection, assetTransferInput.ID, Buffer.from(stringify(sortKeysRecursive(asset)))); // rewrite the asset
console.log(`TransferAsset Put: collection ${assetCollection}, ID ${assetOwner.assetID}`);
await ctx.stub.putPrivateData(assetCollection, assetOwner.assetID, Buffer.from(stringify(sortKeysRecursive(asset)))); // rewrite the asset
// Get collection name for this organization
const ownersCollection = await this.getCollectionName(ctx);
const ownersCollection = this.getCollectionName(ctx);
// Delete the asset appraised value from this organization's private data collection
await ctx.stub.deletePrivateData(ownersCollection, assetTransferInput.ID);
await ctx.stub.deletePrivateData(ownersCollection, assetOwner.assetID);
// Delete the transfer agreement from the asset collection
const transferAgreeKey = ctx.stub.createCompositeKey(transferAgreementObjectType, [assetTransferInput.ID]);
const transferAgreeKey = ctx.stub.createCompositeKey(transferAgreementObjectType, [assetOwner.assetID]);
await ctx.stub.deletePrivateData(assetCollection, transferAgreeKey);
return null;
}
@Transaction()
// DeleteAsset can be used by the owner of the asset to delete the asset
public async DeleteAsset(ctx: Context): Promise<void> {
// Value is private, therefore it gets passed in transient field
const transientMap = ctx.stub.getTransient();
// Persist the JSON bytes as-is so that there is no risk of nondeterministic marshaling.
const valueJSONasBytes = transientMap.get('asset_delete');
if (valueJSONasBytes.length === 0) {
throw new Error('asset to delete not found in the transient map');
}
const jsonBytesToString = String.fromCharCode(...valueJSONasBytes);
const jsonFromString = JSON.parse(jsonBytesToString);
if (jsonFromString.assetID.length === 0) {
throw new Error('assetID field must be a non-empty string');
}
const assetDelete = new TransientAssetDelete(transientMap);
// Verify that the client is submitting request to peer in their organization
const err = await this.verifyClientOrgMatchesPeerOrg(ctx);
if (err !== null) {
throw new Error('DeleteAsset cannot be performed: Error ' + err);
}
console.log('Deleting Asset: ' + jsonFromString.assetID);
this.verifyClientOrgMatchesPeerOrg(ctx);
console.log('Deleting Asset: ' + assetDelete.assetID);
// get the asset from chaincode state
const valAsbytes = await ctx.stub.getPrivateData(assetCollection, jsonFromString.assetID);
const valAsbytes = await ctx.stub.getPrivateData(assetCollection, assetDelete.assetID);
if (valAsbytes.length === 0) {
throw new Error('asset not found: ' + jsonFromString.assetID);
throw new Error('asset not found: ' + assetDelete.assetID);
}
const ownerCollection = await this.getCollectionName(ctx);
const ownerCollection = this.getCollectionName(ctx);
// Check the asset is in the caller org's private collection
const valAsbytesPrivate = await ctx.stub.getPrivateData(ownerCollection, jsonFromString.assetID);
const valAsbytesPrivate = await ctx.stub.getPrivateData(ownerCollection, assetDelete.assetID);
if (valAsbytesPrivate.length === 0) {
throw new Error(`asset not found in owner's private Collection: ${ownerCollection} : ${jsonFromString.assetID}`);
throw new Error(`asset not found in owner's private Collection: ${ownerCollection} : ${assetDelete.assetID}`);
}
// delete the asset from state
await ctx.stub.deletePrivateData(assetCollection, jsonFromString.assetID);
await ctx.stub.deletePrivateData(assetCollection, assetDelete.assetID);
// Finally, delete private details of asset
await ctx.stub.deletePrivateData(ownerCollection, jsonFromString.assetID);
return null;
await ctx.stub.deletePrivateData(ownerCollection, assetDelete.assetID);
}
@Transaction()
// PurgeAsset can be used by the owner of the asset to delete the asset
// Trigger removal of the asset
public async PurgeAsset(ctx: Context): Promise<void> {
// Value is private, therefore it gets passed in transient field
const transientMap = ctx.stub.getTransient();
// Persist the JSON bytes as-is so that there is no risk of nondeterministic marshaling.
const valueJSONasBytes = transientMap.get('asset_purge');
if (valueJSONasBytes.length === 0) {
throw new Error('asset to purge not found in the transient map');
}
const jsonBytesToString = String.fromCharCode(...valueJSONasBytes);
const jsonFromString = JSON.parse(jsonBytesToString);
if (jsonFromString.assetID.length === 0) {
throw new Error('assetID field must be a non-empty string');
}
const assetPurge = new TransientAssetPurge(transientMap);
// Verify that the client is submitting request to peer in their organization
const err = await this.verifyClientOrgMatchesPeerOrg(ctx);
if (err !== null) {
throw new Error('PurgeAsset cannot be performed: Error ' + err);
}
console.log('Purging Asset: ' + jsonFromString.assetID);
this.verifyClientOrgMatchesPeerOrg(ctx);
console.log('Purging Asset: ' + assetPurge.assetID);
// Note that there is no check here to see if the id exist; it might have been 'deleted' already
// so a check here is pointless. We would need to call purge irrespective of the result
// A delete can be called before purge, but is not essential
const ownerCollection = await this.getCollectionName(ctx);
const ownerCollection = this.getCollectionName(ctx);
// delete the asset from state
await ctx.stub.purgePrivateData(assetCollection, jsonFromString.assetID);
await ctx.stub.purgePrivateData(assetCollection, assetPurge.assetID);
// Finally, delete private details of asset
await ctx.stub.purgePrivateData(ownerCollection, jsonFromString.assetID);
return null;
await ctx.stub.purgePrivateData(ownerCollection, assetPurge.assetID);
}
@Transaction()
// DeleteTranferAgreement can be used by the buyer to withdraw a proposal from
// the asset collection and from his own collection.
public async DeleteTransferAgreement(ctx: Context): Promise<void> {
// Value is private, therefore it gets passed in transient field
const transientMap = ctx.stub.getTransient();
// Persist the JSON bytes as-is so that there is no risk of nondeterministic marshaling.
const valueJSONasBytes = transientMap.get('agreement_delete');
if (valueJSONasBytes.length === 0) {
throw new Error('agreement to delete not found in the transient map');
}
const jsonBytesToString = String.fromCharCode(...valueJSONasBytes);
const jsonFromString = JSON.parse(jsonBytesToString);
if (jsonFromString.assetID.length === 0) {
throw new Error('assetID field must be a non-empty string');
}
const agreementDelete = new TransientAssetDelete(transientMap);
// Verify that the client is submitting request to peer in their organization
const err = await this.verifyClientOrgMatchesPeerOrg(ctx);
if (err !== null) {
throw new Error('DeleteTranferAgreement cannot be performed: Error ' + err);
}
this.verifyClientOrgMatchesPeerOrg(ctx);
// Delete private details of agreement
// Get proposers collection.
const orgCollection = await this.getCollectionName(ctx);
const orgCollection = this.getCollectionName(ctx);
// Delete the transfer agreement from the asset collection
const transferAgreeKey = ctx.stub.createCompositeKey(transferAgreementObjectType, [jsonFromString.assetID]);
const transferAgreeKey = ctx.stub.createCompositeKey(transferAgreementObjectType, [agreementDelete.assetID]);
// get the transfer_agreement
const valAsbytes = await ctx.stub.getPrivateData(assetCollection, transferAgreeKey);
if (valAsbytes.length === 0) {
throw new Error(`asset's transfer_agreement does not exist: ${jsonFromString.assetID}`);
throw new Error(`asset's transfer_agreement does not exist: ${agreementDelete.assetID}`);
}
// Delete the asset
await ctx.stub.deletePrivateData(orgCollection, jsonFromString.assetID);
await ctx.stub.deletePrivateData(orgCollection, agreementDelete.assetID);
// Delete transfer agreement record, remove agreement from state
await ctx.stub.deletePrivateData(assetCollection, transferAgreeKey);
return null;
}
/*
GETTERS
*/
// ReadAsset reads the information from collection
@Transaction()
public async ReadAsset(ctx: Context, id: string): Promise<Asset> {
@ -303,33 +217,21 @@ export class AssetTransfer extends Contract {
if (assetAsBytes.length === 0) {
throw new Error(id + ' does not exist in collection ' + assetCollection);
}
const jsonBytesToString = String.fromCharCode(...assetAsBytes);
const jsonFromBytes = JSON.parse(jsonBytesToString);
const asset: Asset = {
ID: jsonFromBytes.ID,
Color: jsonFromBytes.Color,
Size: jsonFromBytes.Size,
Owner: jsonFromBytes.Owner,
};
return asset;
return Asset.fromBytes(assetAsBytes);
}
// ReadAssetPrivateDetails reads the asset private details in organization specific collection
@Transaction()
public async ReadAssetPrivateDetails(ctx: Context, collection: string, id: string): Promise<AssetPrivateDetails> {
// Check if asset already exists
const assetAsBytes = await ctx.stub.getPrivateData(collection, id);
const detailBytes = await ctx.stub.getPrivateData(collection, id);
// No Asset found, return empty response
if (assetAsBytes.length === 0) {
if (detailBytes.length === 0) {
throw new Error(id + ' does not exist in collection ' + collection);
}
const jsonBytesToString = String.fromCharCode(...assetAsBytes);
const jsonFromBytes = JSON.parse(jsonBytesToString);
const asset: AssetPrivateDetails = {
ID: jsonFromBytes.ID,
AppraisedValue: jsonFromBytes.AppraisedValue,
};
return asset;
return AssetPrivateDetails.fromBytes(detailBytes);
}
// ReadTransferAgreement gets the buyer's identity from the transfer agreement from collection
@Transaction()
public async ReadTransferAgreement(ctx: Context, assetID: string): Promise<TransferAgreement> {
@ -341,11 +243,11 @@ export class AssetTransfer extends Contract {
if (buyerIdentity.length === 0) {
throw new Error(`TransferAgreement for ${assetID} does not exist `);
}
const agreement: TransferAgreement = {
return {
ID: assetID,
BuyerID: String(buyerIdentity),
};
return agreement;
}
// GetAssetByRange performs a range query based on the start and end keys provided. Range
@ -356,14 +258,8 @@ export class AssetTransfer extends Contract {
const resultsIterator = ctx.stub.getPrivateDataByRange(assetCollection, startKey, endKey);
const results: Asset[] = [];
for await (const res of resultsIterator) {
const resBytesToString = String.fromCharCode(...res.value);
const jsonFromString = JSON.parse(resBytesToString);
results.push({
ID: jsonFromString.ID,
Color: jsonFromString.Color,
Size: jsonFromString.Size,
Owner: jsonFromString.Owner,
});
const asset = Asset.fromBytes(res.value);
results.push(asset);
}
return results;
}
@ -373,7 +269,7 @@ export class AssetTransfer extends Contract {
// verifyAgreement is an internal helper function used by TransferAsset to verify
// that the transfer is being initiated by the owner and that the buyer has agreed
// to the same appraisal value as the owner
public async verifyAgreement(ctx: Context, assetID: string, owner: string, buyerMSP: string): (Promise<void>) {
public async verifyAgreement(ctx: Context, assetID: string, owner: string, buyerMSP: string): Promise<void> {
// Check 1: verify that the transfer is being initiatied by the owner
// Get ID of submitting client identity
const clientID = ctx.clientIdentity.getID();
@ -382,7 +278,7 @@ export class AssetTransfer extends Contract {
}
// Check 2: verify that the buyer has agreed to the appraised value
// Get collection names
const collectionOwner = await this.getCollectionName(ctx); // get owner collection from caller identity
const collectionOwner = this.getCollectionName(ctx); // get owner collection from caller identity
const collectionBuyer = buyerMSP + 'PrivateCollection'; // get buyers collection
// Get hash of owners agreed to value
@ -394,16 +290,15 @@ export class AssetTransfer extends Contract {
// Get hash of buyers agreed to value
const buyerAppraisedValueHash = await ctx.stub.getPrivateDataHash(collectionBuyer, assetID);
if (buyerAppraisedValueHash.length === 0) {
throw new Error(`hash of appraised value for ${assetID} does not exist in collection ${buyerAppraisedValueHash} . AgreeToTransfer must be called by the buyer first`);
throw new Error(`hash of appraised value for ${assetID} does not exist in collection ${collectionBuyer}. AgreeToTransfer must be called by the buyer first`);
}
// Verify that the two hashes match
if (ownerAppraisedValueHash.toString() !== buyerAppraisedValueHash.toString()) {
throw new Error(`hash for appraised value for owner ${ownerAppraisedValueHash} does not value for seller ${buyerAppraisedValueHash}`);
throw new Error(`hash for appraised value for owner ${Buffer.from(ownerAppraisedValueHash).toString('hex')} does not match value for seller ${Buffer.from(buyerAppraisedValueHash).toString('hex')}`);
}
return null;
}
// getCollectionName is an internal helper function to get collection of submitting client identity.
public async getCollectionName(ctx: Context): Promise<string> {
public getCollectionName(ctx: Context): string {
// Get the MSP ID of submitting client identity
const clientMSPID = ctx.clientIdentity.getMSPID();
// Create the collection name
@ -412,7 +307,7 @@ export class AssetTransfer extends Contract {
return orgCollection;
}
// Get ID of submitting client identity
public async submittingClientIdentity(ctx: Context): (Promise<string>) {
public submittingClientIdentity(ctx: Context): string {
const b64ID = ctx.clientIdentity.getID();
@ -422,7 +317,7 @@ export class AssetTransfer extends Contract {
return String(decodeID);
}
// verifyClientOrgMatchesPeerOrg is an internal function used verify client org id and matches peer org id.
public async verifyClientOrgMatchesPeerOrg(ctx: Context): (Promise<void>) {
public verifyClientOrgMatchesPeerOrg(ctx: Context): void {
const clientMSPID = ctx.clientIdentity.getMSPID();
@ -431,9 +326,6 @@ export class AssetTransfer extends Contract {
if (clientMSPID !== peerMSPID) {
throw new Error('client from org %v is not authorized to read or write private data from an org ' + clientMSPID + ' peer ' + peerMSPID);
}
return null;
}
// =======Rich queries =========================================================================
// Two examples of rich queries are provided below (parameterized query and ad hoc query).
@ -457,13 +349,15 @@ export class AssetTransfer extends Contract {
// =========================================================================================
public async QueryAssetByOwner(ctx: Context, assetType: string, owner: string): Promise<Asset[]> {
const queryString = `{\'selector\':{\'objectType\':\'${assetType}\',\'owner\':\'${owner}\'}}`;
const queryString = `{'selector':{'objectType':'${assetType}','owner':'${owner}'}}`;
return await this.getQueryResultForQueryString(ctx, queryString);
}
public async QueryAssets(ctx: Context, queryString: string): Promise<Asset[]> {
return await this.getQueryResultForQueryString(ctx, queryString);
public QueryAssets(ctx: Context, queryString: string): Promise<Asset[]> {
return this.getQueryResultForQueryString(ctx, queryString);
}
public async getQueryResultForQueryString(ctx: Context, queryString: string): Promise<Asset[]> {
const resultsIterator = ctx.stub.getPrivateDataQueryResult(assetCollection, queryString);
@ -471,14 +365,8 @@ export class AssetTransfer extends Contract {
const results: Asset[] = [];
for await (const res of resultsIterator) {
const resBytesToString = String.fromCharCode(...res.value);
const jsonFromString = JSON.parse(resBytesToString);
results.push({
ID: jsonFromString.ID,
Color: jsonFromString.Color,
Size: jsonFromString.Size,
Owner: jsonFromString.Owner,
});
const asset = Asset.fromBytes(res.value);
results.push(asset);
}
return results;

View file

@ -2,13 +2,31 @@
SPDX-License-Identifier: Apache-2.0
*/
import { Object, Property } from 'fabric-contract-api';
import { Object, Property } from "fabric-contract-api";
import { nonEmptyString, positiveNumber } from "./utils";
@Object()
// AssetPrivateDetails describes details that are private to owners
export class AssetPrivateDetails {
@Property()
public ID: string;
ID: string = "";
@Property()
public AppraisedValue: number;
AppraisedValue: number = 0;
static fromBytes(bytes: Uint8Array): AssetPrivateDetails {
if (bytes.length === 0) {
throw new Error("no asset private details");
}
const json = Buffer.from(bytes).toString();
const properties = JSON.parse(json) as Partial<AssetPrivateDetails>;
const result = new AssetPrivateDetails();
result.ID = nonEmptyString(properties.ID, "ID field must be a non-empty string");
result.AppraisedValue = positiveNumber(
properties.AppraisedValue,
"AppraisedValue field must be a positive integer"
);
return result;
}
}

View file

@ -2,12 +2,114 @@
SPDX-License-Identifier: Apache-2.0
*/
import { Object, Property } from 'fabric-contract-api';
import { nonEmptyString, positiveNumber } from "./utils";
@Object()
export class AssetTransferTransientInput {
@Property()
public ID: string;
@Property()
public BuyerMSP: string;
export class TransientAssetProperties {
objectType: string;
assetID: string;
color: string;
size: number;
appraisedValue: number;
constructor(transientMap: Map<string, Uint8Array>) {
const transient = transientMap.get("asset_properties");
if (!transient?.length) {
throw new Error("no asset properties");
}
const json = Buffer.from(transient).toString();
const properties = JSON.parse(json) as Partial<TransientAssetProperties>;
this.objectType = nonEmptyString(properties.objectType, "objectType field must be a non-empty string");
this.assetID = nonEmptyString(properties.assetID, "assetID field must be a non-empty string");
this.color = nonEmptyString(properties.color, "color field must be a non-empty string");
this.size = positiveNumber(properties.size, "size field must be a positive integer");
this.appraisedValue = positiveNumber(
properties.appraisedValue,
"appraisedValue field must be a positive integer"
);
}
}
export class TransientAssetValue {
assetID: string;
appraisedValue: number;
constructor(transientMap: Map<string, Uint8Array>) {
const transient = transientMap.get("asset_value");
if (!transient?.length) {
throw new Error("no asset value");
}
const json = Buffer.from(transient).toString();
const properties = JSON.parse(json) as Partial<TransientAssetValue>;
this.assetID = nonEmptyString(properties.assetID, "assetID field must be a non-empty string");
this.appraisedValue = positiveNumber(
properties.appraisedValue,
"appraisedValue field must be a positive integer"
);
}
}
export class TransientAssetOwner {
assetID: string;
buyerMSP: string;
constructor(transientMap: Map<string, Uint8Array>) {
const transient = transientMap.get("asset_owner");
if (!transient?.length) {
throw new Error("no asset owner");
}
const json = Buffer.from(transient).toString();
const properties = JSON.parse(json) as Partial<TransientAssetOwner>;
this.assetID = nonEmptyString(properties.assetID, "assetID field must be a non-empty string");
this.buyerMSP = nonEmptyString(properties.buyerMSP, "buyerMSP field must be a non-empty string");
}
}
export class TransientAssetDelete {
assetID: string;
constructor(transientMap: Map<string, Uint8Array>) {
const transient = transientMap.get("asset_delete");
if (!transient?.length) {
throw new Error("no asset delete");
}
const json = Buffer.from(transient).toString();
const properties = JSON.parse(json) as Partial<TransientAssetOwner>;
this.assetID = nonEmptyString(properties.assetID, "assetID field must be a non-empty string");
}
}
export class TransientAssetPurge {
assetID: string;
constructor(transientMap: Map<string, Uint8Array>) {
const transient = transientMap.get("asset_purge");
if (!transient?.length) {
throw new Error("no asset purge");
}
const json = Buffer.from(transient).toString();
const properties = JSON.parse(json) as Partial<TransientAssetOwner>;
this.assetID = nonEmptyString(properties.assetID, "assetID field must be a non-empty string");
}
}
export class TransientAgreementDelete {
assetID: string;
constructor(transientMap: Map<string, Uint8Array>) {
const transient = transientMap.get("agreement_delete");
if (!transient?.length) {
throw new Error("no agreement delete");
}
const json = Buffer.from(transient).toString();
const properties = JSON.parse(json) as Partial<TransientAssetOwner>;
this.assetID = nonEmptyString(properties.assetID, "assetID field must be a non-empty string");
}
}

View file

@ -2,8 +2,9 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { type Contract } from 'fabric-contract-api';
import {AssetTransfer} from './assetTransfer';
export {AssetTransfer} from './assetTransfer';
export const contracts: any[] = [AssetTransfer];
export const contracts: typeof Contract[] = [AssetTransfer];

View file

@ -2,14 +2,13 @@
SPDX-License-Identifier: Apache-2.0
*/
import { Object, Property } from 'fabric-contract-api';
import { Object, Property } from "fabric-contract-api";
@Object()
// TransferAgreement describes the buyer agreement returned by ReadTransferAgreement
export class TransferAgreement {
@Property()
public ID: string;
ID: string = "";
@Property()
public BuyerID: string;
BuyerID: string = "";
}

View file

@ -0,0 +1,19 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
export function nonEmptyString(arg: unknown, errorMessage: string): string {
if (typeof arg !== "string" || arg.length === 0) {
throw new Error(errorMessage);
}
return arg;
}
export function positiveNumber(arg: unknown, errorMessage: string): number {
if (typeof arg !== "number" || arg < 1) {
throw new Error(errorMessage);
}
return arg;
}

View file

@ -1,19 +1,17 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/node18/tsconfig.json",
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"outDir": "dist",
"target": "es2017",
"moduleResolution": "node",
"module": "commonjs",
"esModuleInterop": true,
"declaration": true,
"sourceMap": true
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"strict": true,
"noUnusedLocals": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"./src/**/*"
],
"exclude": [
"./src/**/*.spec.ts"
]
"include": ["src/"]
}

View file

@ -1,23 +0,0 @@
{
"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

@ -1,99 +0,0 @@
module.exports = {
env: {
node: true,
es2020: true,
},
extends: [
'eslint:recommended',
],
root: true,
ignorePatterns: [
'dist/',
],
rules: {
'arrow-spacing': ['error'],
'comma-style': ['error'],
complexity: ['error', 10],
'eol-last': ['error'],
'generator-star-spacing': ['error', 'after'],
'key-spacing': [
'error',
{
beforeColon: false,
afterColon: true,
mode: 'minimum',
},
],
'keyword-spacing': ['error'],
'no-multiple-empty-lines': ['error'],
'no-trailing-spaces': ['error'],
'no-whitespace-before-property': ['error'],
'object-curly-newline': ['error'],
'padded-blocks': ['error', 'never'],
'rest-spread-spacing': ['error'],
'semi-style': ['error'],
'space-before-blocks': ['error'],
'space-in-parens': ['error'],
'space-unary-ops': ['error'],
'spaced-comment': ['error'],
'template-curly-spacing': ['error'],
'yield-star-spacing': ['error', 'after'],
},
overrides: [
{
files: [
'**/*.ts',
],
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
ecmaFeatures: {
impliedStrict: true,
},
project: './tsconfig.json',
tsconfigRootDir: process.env.TSCONFIG_ROOT_DIR || __dirname,
},
plugins: [
'@typescript-eslint',
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
],
rules: {
'@typescript-eslint/comma-spacing': ['error'],
'@typescript-eslint/explicit-function-return-type': [
'error',
{
allowExpressions: true,
},
],
'@typescript-eslint/func-call-spacing': ['error'],
'@typescript-eslint/member-delimiter-style': ['error'],
'@typescript-eslint/indent': [
'error',
4,
{
SwitchCase: 0,
},
],
'@typescript-eslint/prefer-nullish-coalescing': ['error'],
'@typescript-eslint/prefer-optional-chain': ['error'],
'@typescript-eslint/prefer-reduce-type-parameter': ['error'],
'@typescript-eslint/prefer-return-this-type': ['error'],
'@typescript-eslint/quotes': ['error', 'single'],
'@typescript-eslint/type-annotation-spacing': ['error'],
'@typescript-eslint/semi': ['error'],
'@typescript-eslint/space-before-function-paren': [
'error',
{
anonymous: 'never',
named: 'never',
asyncArrow: 'always',
},
],
},
},
],
};

View file

@ -0,0 +1,13 @@
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked, {
languageOptions: {
ecmaVersion: 2023,
sourceType: 'module',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: import.meta.dirname,
},
},
});

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,7 @@
"node": ">=18"
},
"scripts": {
"lint": "eslint ./src --ext .ts",
"lint": "eslint src",
"pretest": "npm run lint",
"test": "echo 'No tests implemented'",
"start": "fabric-chaincode-node start",
@ -21,15 +21,15 @@
"author": "Hyperledger",
"license": "Apache-2.0",
"dependencies": {
"fabric-contract-api": "~2.5.4",
"fabric-shim": "~2.5.4"
"fabric-contract-api": "~2.5.5",
"fabric-shim": "~2.5.5"
},
"devDependencies": {
"@tsconfig/node18": "^18.2.2",
"@types/node": "^18.17.17",
"@typescript-eslint/eslint-plugin": "^6.7.0",
"@typescript-eslint/parser": "^6.7.0",
"eslint": "^8.49.0",
"typescript": "~5.2.0"
"@types/node": "^18.19.33",
"@eslint/js": "^9.3.0",
"@tsconfig/node18": "^18.2.4",
"eslint": "^8.57.0",
"typescript": "~5.4.5",
"typescript-eslint": "^7.11.0"
}
}

View file

@ -90,7 +90,7 @@ export class AssetContract extends Contract {
// AssetExists returns true when asset with given ID exists
public async AssetExists(ctx: Context, assetId: string): Promise<boolean> {
const buffer = await ctx.stub.getState(assetId);
return (!!buffer && buffer.length > 0);
return buffer.length > 0;
}
// getClientOrgId gets the client's OrgId (MSPID)

View file

@ -2,16 +2,16 @@
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/node18/tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"declaration": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"sourceMap": true
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"strict": true,
"noUnusedLocals": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"./src/**/*"
],
"exclude": [
"./src/**/*.spec.ts"
]
"include": ["src/"]
}