Secured agreement samples using gateway (#630)

* initial commit

Signed-off-by: sapthasurendran <saptha.surendran@ibm.com>

Code refactor

Signed-off-by: sapthasurendran <saptha.surendran@ibm.com>

* readme

Signed-off-by: sapthasurendran <saptha.surendran@ibm.com>

* lint fix

Signed-off-by: sapthasurendran <saptha.surendran@ibm.com>

* adopted best practises

Signed-off-by: sapthasurendran <saptha.surendran@ibm.com>

* code refactor

Signed-off-by: sapthasurendran <saptha.surendran@ibm.com>

* updated azure pipeline to include the app

Signed-off-by: sapthasurendran <saptha.surendran@ibm.com>

* mapped json and client object

Signed-off-by: sapthasurendran <saptha.surendran@ibm.com>

* Moved contract interactions to contractWrapper

Signed-off-by: sapthasurendran <saptha.surendran@ibm.com>

* salt value unexported

Signed-off-by: sapthasurendran <saptha.surendran@ibm.com>

* moved try catch from contract wrapper to app

Signed-off-by: sapthasurendran <saptha.surendran@ibm.com>

* contract wrapper refactor
moved interfaces from utils to contract wrapper

Signed-off-by: sapthasurendran <saptha.surendran@ibm.com>

* exported data objects

Signed-off-by: sapthasurendran <saptha.surendran@ibm.com>
This commit is contained in:
sapthasurendran 2022-05-23 18:47:10 +05:30 committed by GitHub
parent 8662b10c58
commit 4681fe7865
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 847 additions and 0 deletions

View file

@ -0,0 +1,64 @@
# Asset transfer secured agreement sample
The asset transfer events sample demonstrates how to transfer a private asset between two organizations without publicly sharing data .
## About the sample
This sample includes smart contract and application code in multiple languages. This sample shows how Fabric features state based endorsement, private data, and access control to provide secured transactions.
### Application
Refer [Secured asset transfer in Fabric](https://hyperledger-fabric.readthedocs.io/en/latest/secured_asset_transfer/secured_private_asset_transfer_tutorial.html) for application details .
### Smart Contract
The smart contract (in folder `chaincode-go`) implements the following functions to support the application:
- CreateAsset
- ChangePublicDescription
- AgreeToSell
- AgreeToBuy
- VerifyAssetProperties
- TransferAsset
- ReadAsset
- GetAssetPrivateProperties
- GetAssetSalesPrice
- GetAssetBidPrice
- QueryAssetSaleAgreements
- QueryAssetBuyAgreements
- QueryAssetHistory
## Running the sample
Like other samples, the Fabric test network is used to deploy and run this sample. Follow these steps in order:
1. Create the test network and a channel (from the `test-network` folder).
```
./network.sh up createChannel -c mychannel -ca
```
1. Deploy the smart contract implementations.
```
# To deploy the go chaincode implementation
./network.sh deployCC -ccn secured -ccp ../asset-transfer-secured-agreement/chaincode-go/ -ccl go -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
```
1. Run the application (from the `asset-transfer-secured-agreement` folder).
```
# To run the Typescript sample application
cd application-gateway-typescript
npm install
npm start
# To run the Javascript sample application
cd application-javascript
node app.js
```
## Clean up
When you are finished, you can bring down the test network (from the `test-network` folder). The command will remove all the nodes of the test network, and delete any ledger data that you created.
```
./network.sh down
```

View file

@ -0,0 +1,102 @@
{
"env": {
"node": true,
"es6": true
},
"root": true,
"ignorePatterns": [
"dist/"
],
"extends": [
"eslint:recommended"
],
"rules": {
"indent": [
"error",
4
],
"quotes": [
"error",
"single"
]
},
"overrides": [
{
"files": [
"**/*.ts"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"impliedStrict": true
},
"project": [
"./tsconfig.json"
]
},
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"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,14 @@
#
# SPDX-License-Identifier: Apache-2.0
#
# Coverage directory used by tools like istanbul
coverage
# Dependency directories
node_modules/
jspm_packages/
# Compiled TypeScript files
dist

View file

@ -0,0 +1,32 @@
{
"name": "asset-transfer-basic",
"version": "1.0.0",
"description": "Asset Transfer Secured Agreement Application implemented in typeScript using fabric-gateway",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"engines": {
"node": ">=14"
},
"scripts": {
"build": "tsc",
"build:watch": "tsc -w",
"lint": "eslint . --ext .ts",
"prepare": "npm run build",
"pretest": "npm run lint",
"start": "node dist/app.js"
},
"engineStrict": true,
"author": "Hyperledger",
"license": "Apache-2.0",
"dependencies": {
"@hyperledger/fabric-gateway": "^1.0.0",
"@grpc/grpc-js": "^1.5.0"
},
"devDependencies": {
"@tsconfig/node14": "^1.0.1",
"@typescript-eslint/eslint-plugin": "^5.6.0",
"@typescript-eslint/parser": "^5.6.0",
"eslint": "^8.4.1",
"typescript": "~4.5.2"
}
}

View file

@ -0,0 +1,216 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import { connect } from '@hyperledger/fabric-gateway';
import { newGrpcConnection, newIdentity, newSigner, tlsCertPathOrg1, peerEndpointOrg1, peerNameOrg1, certPathOrg1, mspIdOrg1, keyDirectoryPathOrg1, tlsCertPathOrg2, peerEndpointOrg2, peerNameOrg2, certPathOrg2, mspIdOrg2, keyDirectoryPathOrg2 } from './connect';
import { ContractWrapper } from './contractWrapper';
import { RED, RESET } from './utils';
const channelName = 'mychannel';
const chaincodeName = 'secured';
//Use a random key so that we can run multiple times
const now = Date.now().toString();
const assetKey = `asset${now}`;
async function main(): Promise<void> {
// The gRPC client connection from org1 should be shared by all Gateway connections to this endpoint.
const clientOrg1 = await newGrpcConnection(
tlsCertPathOrg1,
peerEndpointOrg1,
peerNameOrg1
);
const gatewayOrg1 = connect({
client: clientOrg1,
identity: await newIdentity(certPathOrg1, mspIdOrg1),
signer: await newSigner(keyDirectoryPathOrg1),
});
// The gRPC client connection from org2 should be shared by all Gateway connections to this endpoint.
const clientOrg2 = await newGrpcConnection(
tlsCertPathOrg2,
peerEndpointOrg2,
peerNameOrg2
);
const gatewayOrg2 = connect({
client: clientOrg2,
identity: await newIdentity(certPathOrg2, mspIdOrg2),
signer: await newSigner(keyDirectoryPathOrg2),
});
try {
// Get the smart contract from the network for Org1.
const contractOrg1 = gatewayOrg1.getNetwork(channelName).getContract(chaincodeName);
const contractWrapperOrg1 = new ContractWrapper(contractOrg1, mspIdOrg1);
// Get the smart contract from the network for Org2.
const contractOrg2 = gatewayOrg2.getNetwork(channelName).getContract(chaincodeName);
const contractWrapperOrg2 = new ContractWrapper(contractOrg2, mspIdOrg2);
// Create an asset by organization Org1, this only requires the owning organization to endorse.
await contractWrapperOrg1.createAsset({ assetId: assetKey,
ownerOrg: mspIdOrg1,
publicDescription: `Asset ${assetKey} owned by ${mspIdOrg1} is not for sale`}, { ObjectType: 'asset_properties', Color: 'blue', Size: 35 });
// Read the public details by org1.
await contractWrapperOrg1.readAsset(assetKey, mspIdOrg1);
// Read the public details by org2.
await contractWrapperOrg2.readAsset(assetKey, mspIdOrg1);
// Org1 should be able to read the private data details of the asset.
await contractWrapperOrg1.getAssetPrivateProperties(assetKey, mspIdOrg1);
// Org2 is not the owner and does not have the private details, read expected to fail.
try {
await contractWrapperOrg2.getAssetPrivateProperties(assetKey, mspIdOrg1);
} catch(e) {
console.log(`${RED}*** Failed: getAssetPrivateProperties - ${e}${RESET}`);
}
// Org1 updates the assets public description.
await contractWrapperOrg1.changePublicDescription({assetId: assetKey,
ownerOrg: mspIdOrg1,
publicDescription: `Asset ${assetKey} owned by ${mspIdOrg1} is for sale`});
// Read the public details by org1.
await contractWrapperOrg1.readAsset(assetKey, mspIdOrg1);
// Read the public details by org2.
await contractWrapperOrg2.readAsset(assetKey, mspIdOrg1);
// This is an update to the public state and requires the owner(Org1) to endorse and sent by the owner org client (Org1).
// Since the client is from Org2, which is not the owner, this will fail.
try{
await contractWrapperOrg2.changePublicDescription({assetId: assetKey,
ownerOrg: mspIdOrg1,
publicDescription: `Asset ${assetKey} owned by ${mspIdOrg2} is NOT for sale`});
} catch(e) {
console.log(`${RED}*** Failed: changePublicDescription - ${e}${RESET}`);
}
// Read the public details by org1.
await contractWrapperOrg1.readAsset(assetKey, mspIdOrg1);
// Read the public details by org2.
await contractWrapperOrg2.readAsset(assetKey, mspIdOrg1);
// Agree to a sell by org1.
await contractWrapperOrg1.agreeToSell({
assetId: assetKey,
price: 110,
tradeId: now,
});
// Check the private information about the asset from Org2. Org1 would have to send Org2 asset details,
// so the hash of the details may be checked by the chaincode.
await contractWrapperOrg2.verifyAssetProperties({ assetId:assetKey, color:'blue', size:35});
// Agree to a buy by org2.
await contractWrapperOrg2.agreeToBuy( {assetId: assetKey,
price: 100,
tradeId: now});
// Org1 should be able to read the sale price of this asset.
await contractWrapperOrg1.getAssetSalesPrice(assetKey, mspIdOrg1);
// Org2 has not set a sale price and this should fail.
try{
await contractWrapperOrg2.getAssetSalesPrice(assetKey, mspIdOrg1);
} catch(e) {
console.log(`${RED}*** Failed: getAssetSalesPrice - ${e}${RESET}`);
}
// Org1 has not agreed to buy so this should fail.
try{
await contractWrapperOrg1.getAssetBidPrice(assetKey, mspIdOrg2);
} catch(e) {
console.log(`${RED}*** Failed: getAssetBidPrice - ${e}${RESET}`);
}
// Org2 should be able to see the price it has agreed.
await contractWrapperOrg2.getAssetBidPrice(assetKey, mspIdOrg2);
// Org1 will try to transfer the asset to Org2
// This will fail due to the sell price and the bid price are not the same.
try{
await contractWrapperOrg1.transferAsset({ObjectType: 'asset_properties', Color: 'blue', Size: 35}, { assetId: assetKey, price: 110, tradeId: now}, [ mspIdOrg1, mspIdOrg2 ], mspIdOrg1, mspIdOrg2);
} catch(e) {
console.log(`${RED}*** Failed: transferAsset - ${e}${RESET}`);
}
// Agree to a sell by Org1, the seller will agree to the bid price of Org2.
await contractWrapperOrg1.agreeToSell({assetId:assetKey, price:100, tradeId:now});
// Read the public details by org1.
await contractWrapperOrg1.readAsset(assetKey, mspIdOrg1);
// Read the public details by org2.
await contractWrapperOrg2.readAsset(assetKey, mspIdOrg1);
// Org1 should be able to read the private data details of the asset.
await contractWrapperOrg1.getAssetPrivateProperties(assetKey, mspIdOrg1);
// Org1 should be able to read the sale price of this asset.
await contractWrapperOrg1.getAssetSalesPrice(assetKey, mspIdOrg1);
// Org2 should be able to see the price it has agreed.
await contractWrapperOrg2.getAssetBidPrice(assetKey, mspIdOrg2);
// Org2 user will try to transfer the asset to Org1.
// This will fail as the owner is Org1.
try{
await contractWrapperOrg2.transferAsset({ObjectType: 'asset_properties', Color: 'blue', Size: 35}, { assetId: assetKey, price: 100, tradeId: now}, [ mspIdOrg1, mspIdOrg2 ], mspIdOrg1, mspIdOrg2);
} catch(e) {
console.log(`${RED}*** Failed: transferAsset - ${e}${RESET}`);
}
// Org1 will transfer the asset to Org2.
// This will now complete as the sell price and the bid price are the same.
await contractWrapperOrg1.transferAsset({ObjectType: 'asset_properties', Color: 'blue', Size: 35}, { assetId: assetKey, price: 100, tradeId: now}, [ mspIdOrg1, mspIdOrg2 ], mspIdOrg1, mspIdOrg2);
// Read the public details by org1.
await contractWrapperOrg1.readAsset(assetKey, mspIdOrg2);
// Read the public details by org2.
await contractWrapperOrg2.readAsset(assetKey, mspIdOrg2);
// Org2 should be able to read the private data details of this asset.
await contractWrapperOrg2.getAssetPrivateProperties(assetKey, mspIdOrg2);
// Org1 should not be able to read the private data details of this asset, expected to fail.
try{
await contractWrapperOrg1.getAssetPrivateProperties(assetKey, mspIdOrg2);
} catch(e) {
console.log(`${RED}*** Failed: getAssetPrivateProperties - ${e}${RESET}`);
}
// This is an update to the public state and requires only the owner to endorse.
// Org2 wants to indicate that the items is no longer for sale.
await contractWrapperOrg2.changePublicDescription( {assetId: assetKey, ownerOrg: mspIdOrg2, publicDescription: `Asset ${assetKey} owned by ${mspIdOrg2} is NOT for sale`});
// Read the public details by org1.
await contractWrapperOrg1.readAsset(assetKey, mspIdOrg2);
// Read the public details by org2.
await contractWrapperOrg2.readAsset(assetKey, mspIdOrg2);
} finally {
gatewayOrg1.close();
gatewayOrg2.close();
clientOrg1.close();
clientOrg2.close();
}
}
main().catch(error => {
console.error('******** FAILED to run the application:', error);
process.exitCode = 1;
});

View file

@ -0,0 +1,103 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import * as grpc from '@grpc/grpc-js';
import { Identity, Signer, signers } from '@hyperledger/fabric-gateway';
import * as crypto from 'crypto';
import { promises as fs } from 'fs';
import * as path from 'path';
// MSP Id's of Organizations
export const mspIdOrg1 = 'Org1MSP';
export const mspIdOrg2 = 'Org2MSP';
// Path to org1 crypto materials.
export const cryptoPathOrg1 = path.resolve(__dirname, '..', '..', '..', 'test-network', 'organizations', 'peerOrganizations', 'org1.example.com');
// Path to user private key directory.
export const keyDirectoryPathOrg1 = path.resolve(cryptoPathOrg1, 'users', 'User1@org1.example.com', 'msp', 'keystore');
// Path to user certificate.
export const certPathOrg1 = path.resolve(cryptoPathOrg1, 'users', 'User1@org1.example.com', 'msp', 'signcerts', 'cert.pem');
// Path to peer tls certificate.
export const tlsCertPathOrg1 = path.resolve(cryptoPathOrg1, 'peers', 'peer0.org1.example.com', 'tls', 'ca.crt');
// Path to org2 crypto materials.
export const cryptoPathOrg2 = path.resolve(
__dirname,
'..',
'..',
'..',
'test-network',
'organizations',
'peerOrganizations',
'org2.example.com'
);
// Path to org2 user private key directory.
export const keyDirectoryPathOrg2 = path.resolve(
cryptoPathOrg2,
'users',
'User1@org2.example.com',
'msp',
'keystore'
);
// Path to org2 user certificate.
export const certPathOrg2 = path.resolve(
cryptoPathOrg2,
'users',
'User1@org2.example.com',
'msp',
'signcerts',
'cert.pem'
);
// Path to org2 peer tls certificate.
export const tlsCertPathOrg2 = path.resolve(
cryptoPathOrg2,
'peers',
'peer0.org2.example.com',
'tls',
'ca.crt'
);
// Gateway peer endpoint.
export const peerEndpointOrg1 = 'localhost:7051';
export const peerEndpointOrg2 = 'localhost:9051';
// Gateway peer container name.
export const peerNameOrg1 = 'peer0.org1.example.com';
export const peerNameOrg2 = 'peer0.org2.example.com';
//Collection Names
export const org1PrivateCollectionName = 'Org1MSPPrivateCollection';
export const org2PrivateCollectionName = 'Org2MSPPrivateCollection';
export async function newGrpcConnection(
tlsCertPath: string,
peerEndpoint: string,
peerName: string
): Promise<grpc.Client> {
const tlsRootCert = await fs.readFile(tlsCertPath);
const tlsCredentials = grpc.credentials.createSsl(tlsRootCert);
return new grpc.Client(peerEndpoint, tlsCredentials, {
'grpc.ssl_target_name_override': peerName,
});
}
export async function newIdentity(certPath: string, mspId: string): Promise<Identity> {
const credentials = await fs.readFile(certPath);
return { mspId, credentials };
}
export async function newSigner(keyDirectoryPath: string): Promise<Signer> {
const files = await fs.readdir(keyDirectoryPath);
const keyPath = path.resolve(keyDirectoryPath, files[0]);
const privateKeyPem = await fs.readFile(keyPath);
const privateKey = crypto.createPrivateKey(privateKeyPem);
return signers.newPrivateKeySigner(privateKey);
}

View file

@ -0,0 +1,273 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import { Contract } from '@hyperledger/fabric-gateway';
import { TextDecoder } from 'util';
import { GREEN, parse, RED, RESET } from './utils';
import crpto from 'crypto';
const randomBytes = crpto.randomBytes(256).toString('hex');
interface AssetJSON {
objectType: string;
assetID: string;
ownerOrg: string;
publicDescription: string;
}
interface AssetPropertiesJSON {
objectType: string;
assetID: string;
color: string;
size: number;
salt: string;
}
interface AssetPriceJSON {
assetID: string;
price: number;
tradeID: string;
}
export interface AssetPrivateData {
ObjectType: string;
Color: string;
Size: number;
}
export interface Asset {
assetId: string;
ownerOrg: string;
publicDescription: string;
}
export interface AssetProperties {
assetId: string;
color: string;
size: number;
}
export interface AssetPrice {
assetId: string;
price: number;
tradeId: string;
}
export class ContractWrapper {
readonly #contract: Contract;
readonly #org: string;
readonly #utf8Decoder = new TextDecoder();
readonly #randomBytes: string = randomBytes;
public constructor(contract: Contract, org: string) {
this.#contract = contract;
this.#org = org;
}
public async createAsset(asset: Asset, privateData: AssetPrivateData): Promise<void> {
console.log(`${GREEN}--> Submit Transaction: CreateAsset, ${asset.assetId} as ${asset.ownerOrg} - endorsed by Org1.${RESET}`);
const assetPropertiesJSON: AssetPropertiesJSON = {
objectType: 'asset_properties',
assetID: asset.assetId,
color: privateData.Color,
size: privateData.Size,
salt: this.#randomBytes };
await this.#contract.submit('CreateAsset', {
arguments: [asset.assetId, asset.publicDescription],
transientData: { asset_properties: JSON.stringify(assetPropertiesJSON)},
});
console.log(`*** Result: committed, asset ${asset.assetId} is owned by Org1`);
}
public async readAsset(assetKey: string, ownerOrg: string): Promise<void> {
console.log(`${GREEN}--> Evaluate Transactions: ReadAsset as ${this.#org}, - ${assetKey} should be owned by ${ownerOrg}.${RESET}`);
const resultBytes = await this.#contract.evaluateTransaction('ReadAsset', assetKey);
const result = this.#utf8Decoder.decode(resultBytes);
if (result.length !== 0) {
const json = parse<AssetJSON>(result);
if (json.ownerOrg === ownerOrg) {
console.log(`*** Result from ${this.#org} - asset ${json.assetID} owned by ${json.ownerOrg} DESC: ${json.publicDescription}`);
} else {
console.log(`${RED}*** Failed owner check from ${this.#org} - asset ${json.assetID} owned by ${json.ownerOrg} DESC:${json.publicDescription}.${RESET}`);
}
} else {
throw new Error('No Asset Found');
}
}
public async getAssetPrivateProperties(assetKey: string, ownerOrg: string): Promise<void> {
console.log(`${GREEN}--> Evaluate Transaction: GetAssetPrivateProperties, - ${assetKey} from organization ${this.#org}.${RESET}`);
if(this.#org !== ownerOrg) {
console.log(`${GREEN}* Expected to fail as ${this.#org} is not the owner and does not have the private details.${RESET}`);
}
const resultBytes = await this.#contract.evaluateTransaction('GetAssetPrivateProperties', assetKey);
const resultString = this.#utf8Decoder.decode(resultBytes);
const json = parse<AssetPropertiesJSON>(resultString);
const result: AssetProperties = {
assetId: json.assetID,
color: json.color,
size: json.size,
};
console.log('*** Result:', result);
}
public async changePublicDescription(asset: Asset): Promise<void> {
console.log(`${GREEN}--> Submit Transaction: ChangePublicDescription ${asset.assetId}, as ${this.#org} - endorse by ${this.#org}.${RESET}`);
if (asset.ownerOrg !== this.#org) {
console.log(`${GREEN}* Expected to fail as ${this.#org} is not the owner.${RESET}`);
}
await this.#contract.submit('ChangePublicDescription', {
arguments:[asset.assetId, asset.publicDescription],
});
console.log(`*** Result: committed, Desc: ${asset.publicDescription}`);
}
public async agreeToSell(assetPrice: AssetPrice): Promise<void> {
console.log(`${GREEN}--> Submit Transaction: AgreeToSell, ${assetPrice.assetId} as ${this.#org} - endorsed by ${this.#org}.${RESET}`);
const assetPriceJSON: AssetPriceJSON = {
assetID:assetPrice.assetId,
price:assetPrice.price,
tradeID:assetPrice.tradeId
};
await this.#contract.submit('AgreeToSell', {
arguments:[assetPrice.assetId],
transientData: {asset_price: JSON.stringify(assetPriceJSON)}
});
console.log(`*** Result: committed, ${this.#org} has agreed to sell asset ${assetPrice.assetId} for ${assetPrice.price}`);
}
public async verifyAssetProperties(assetProperties: AssetProperties): Promise<void> {
console.log(`${GREEN}--> Evalute: VerifyAssetProperties, ${assetProperties.assetId} as ${this.#org} - endorsed by ${this.#org}.${RESET}`);
const assetPropertiesJSON: AssetPropertiesJSON = {objectType: 'asset_properties',
assetID: assetProperties.assetId,
color: assetProperties.color,
size: assetProperties.size,
salt: this.#randomBytes };
const resultBytes = await this.#contract.evaluate('VerifyAssetProperties', {
arguments:[assetPropertiesJSON.assetID],
transientData: {asset_properties: JSON.stringify(assetPropertiesJSON)},
});
const resultString = this.#utf8Decoder.decode(resultBytes);
if (resultString.length !== 0) {
const json = parse<AssetPropertiesJSON>(resultString);
const result: AssetProperties = {
assetId: json.assetID,
color: json.color,
size: json.size
};
if (result) {
console.log(`*** Success VerifyAssetProperties, private information about asset ${assetProperties.assetId} has been verified by ${this.#org}`);
} else {
console.log(`*** Failed: VerifyAssetProperties, private information about asset ${assetProperties.assetId} has not been verified by ${this.#org}`);
}
} else {
throw new Error(`Private information about asset ${assetProperties.assetId} has not been verified by ${this.#org}`);
}
}
public async agreeToBuy(assetPrice: AssetPrice, ): Promise<void> {
console.log(`${GREEN}--> Submit Transaction: AgreeToBuy, ${assetPrice.assetId} as ${this.#org} - endorsed by ${this.#org}.${RESET}`);
const assetPriceJSON: AssetPriceJSON = {
assetID: assetPrice.assetId,
price: assetPrice.price,
tradeID: assetPrice.tradeId
};
await this.#contract.submit('AgreeToBuy', {
arguments:[assetPrice.assetId],
transientData: {asset_price: JSON.stringify(assetPriceJSON)}
});
console.log(`*** Result: committed, ${this.#org} has agreed to buy asset ${assetPrice.assetId} for 100`);
}
public async getAssetSalesPrice(assetKey: string, ownerOrg: string): Promise<void> {
console.log(`${GREEN}--> Evaluate Transaction: GetAssetSalesPrice, - ${assetKey} from organization ${this.#org}.${RESET}`);
if(this.#org !== ownerOrg) {
console.log(`${GREEN}* Expected to fail as ${this.#org} has not set a sale price.${RESET}`);
}
const resultBytes = await this.#contract.evaluateTransaction('GetAssetSalesPrice', assetKey);
const resultString = this.#utf8Decoder.decode(resultBytes);
const json = parse<AssetPriceJSON>(resultString);
const result: AssetPrice = {
assetId: json.assetID,
price: json.price,
tradeId: json.tradeID
};
console.log('*** Result: GetAssetSalesPrice', result);
}
public async getAssetBidPrice(assetKey: string, buyerOrgID: string): Promise<void> {
console.log(`${GREEN}--> Evaluate Transaction: GetAssetBidPrice, - ${assetKey} from organization ${this.#org}.${RESET}`);
if(this.#org !== buyerOrgID){
console.log(`${GREEN}* Expected to fail as ${this.#org} has not agreed to buy.${RESET}`);
}
const resultBytes = await this.#contract.evaluateTransaction('GetAssetBidPrice', assetKey);
const resultString = this.#utf8Decoder.decode(resultBytes);
const json = parse<AssetPriceJSON>(resultString);
const result: AssetPrice = {
assetId: json.assetID,
price: json.price,
tradeId: json.tradeID,
};
console.log('*** Result: GetAssetBidPrice', result);
}
public async transferAsset( privateData: AssetPrivateData, assetPrice: AssetPrice, endorsingOrganizations: string[], ownerOrgID: string, buyerOrgID: string): Promise<void> {
console.log(`${GREEN}--> Submit Transaction: TransferAsset, ${assetPrice.assetId} as ${this.#org } - endorsed by ${this.#org}.${RESET}`);
if (this.#org !== ownerOrgID) {
console.log(`${GREEN}* Expected to fail as the owner is ${ownerOrgID}.${RESET}`);
} else if (assetPrice.price === 110) {
console.log(`${GREEN}* Expected to fail as sell price and the bid price are not the same.${RESET}`);
}
const assetPropertiesJSON: AssetPropertiesJSON = {objectType: 'asset_properties',
assetID: assetPrice.assetId,
color: privateData.Color,
size: privateData.Size,
salt: this.#randomBytes };
const assetPriceJSON: AssetPriceJSON = { assetID: assetPrice.assetId, price:assetPrice.price, tradeID:assetPrice.tradeId};
await this.#contract.submit('TransferAsset', {
arguments:[assetPropertiesJSON.assetID, buyerOrgID],
transientData: {
asset_properties: JSON.stringify(assetPropertiesJSON),
asset_price: JSON.stringify(assetPriceJSON)},
endorsingOrganizations:endorsingOrganizations
});
console.log(`${GREEN}*** Result: committed, ${this.#org} has transfered the asset ${assetPrice.assetId} to ${buyerOrgID}.${RESET}`);
}
}

View file

@ -0,0 +1,13 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
export const RED = '\x1b[31m\n';
export const GREEN = '\x1b[32m\n';
export const RESET = '\x1b[0m';
export function parse<T>(data: string): T {
return JSON.parse(data);
}

View file

@ -0,0 +1,18 @@
{
"extends":"@tsconfig/node14/tsconfig.json",
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"outDir": "dist",
"moduleResolution": "node",
"declaration": true,
"sourceMap": true,
"noImplicitAny": true
},
"include": [
"./src/**/*"
],
"exclude": [
"./src/**/*.spec.ts"
]
}

View file

@ -34,3 +34,15 @@ popd
stopNetwork
print "Remove wallet storage"
rm -R ../asset-transfer-secured-agreement/application-javascript/wallet
# Run Typescript Gateway application
createNetwork
print "Initializing typescript application"
pushd ../asset-transfer-secured-agreement/application-gateway-typescript
npm install
print "Build app"
npm run build
print "Executing dist/app.js"
npm start
popd
stopNetwork