mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 15:35:09 +00:00
Fix private-data TypeScript chaincode (#1357)
The build is only testing the Go chaincode for the asset-transfer-private-data sample. The behavior of the TypeScript chaincode implementation is not consistent with the Go and Java versions, which prevents it from working correctly. This change fixes the TypeScript chaincode implementation for the asset-transfer-private-data sample so that it works correctly. Additionally, some JSON property names are explicitly set in the Go client application sample, which prevented it from working with the Java and TypeScript chaincode implementations. Signed-off-by: Mark S. Lewis <Mark.S.Lewis@outlook.com>
This commit is contained in:
parent
a72b2b5132
commit
f865a9ea51
7 changed files with 865 additions and 417 deletions
2
.github/workflows/test-network-private.yaml
vendored
2
.github/workflows/test-network-private.yaml
vendored
|
|
@ -22,6 +22,8 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
chaincode-language:
|
chaincode-language:
|
||||||
- go
|
- go
|
||||||
|
- java
|
||||||
|
- typescript
|
||||||
chaincode-name:
|
chaincode-name:
|
||||||
- private
|
- private
|
||||||
steps:
|
steps:
|
||||||
|
|
|
||||||
|
|
@ -162,11 +162,11 @@ func createAssets(contract *client.Contract) {
|
||||||
fmt.Printf("\n--> Submit Transaction: CreateAsset, ID: %s\n", assetID1)
|
fmt.Printf("\n--> Submit Transaction: CreateAsset, ID: %s\n", assetID1)
|
||||||
|
|
||||||
type assetTransientInput struct {
|
type assetTransientInput struct {
|
||||||
ObjectType string
|
ObjectType string `json:"objectType"`
|
||||||
AssetID string
|
AssetID string `json:"assetID"`
|
||||||
Color string
|
Color string `json:"color"`
|
||||||
Size uint8
|
Size uint8 `json:"size"`
|
||||||
AppraisedValue uint16
|
AppraisedValue uint16 `json:"appraisedValue"`
|
||||||
}
|
}
|
||||||
|
|
||||||
asset1Data := assetTransientInput{
|
asset1Data := assetTransientInput{
|
||||||
|
|
@ -300,7 +300,9 @@ func transferAsset(contract *client.Contract, assetID string) (err error) {
|
||||||
func deleteAsset(contract *client.Contract, assetID string) (err error) {
|
func deleteAsset(contract *client.Contract, assetID string) (err error) {
|
||||||
fmt.Printf("\n--> Submit Transaction: DeleteAsset, ID: %s\n", assetID)
|
fmt.Printf("\n--> Submit Transaction: DeleteAsset, ID: %s\n", assetID)
|
||||||
|
|
||||||
dataForDelete := struct{ AssetID string }{assetID}
|
dataForDelete := struct {
|
||||||
|
AssetID string `json:"assetID"`
|
||||||
|
}{assetID}
|
||||||
|
|
||||||
if _, err = contract.Submit(
|
if _, err = contract.Submit(
|
||||||
"DeleteAsset",
|
"DeleteAsset",
|
||||||
|
|
@ -318,7 +320,9 @@ func deleteAsset(contract *client.Contract, assetID string) (err error) {
|
||||||
func purgeAsset(contract *client.Contract, assetID string) (err error) {
|
func purgeAsset(contract *client.Contract, assetID string) (err error) {
|
||||||
fmt.Printf("\n--> Submit Transaction: PurgeAsset, ID: %s\n", assetID)
|
fmt.Printf("\n--> Submit Transaction: PurgeAsset, ID: %s\n", assetID)
|
||||||
|
|
||||||
dataForPurge := struct{ AssetID string }{assetID}
|
dataForPurge := struct {
|
||||||
|
AssetID string `json:"assetID"`
|
||||||
|
}{assetID}
|
||||||
|
|
||||||
if _, err = contract.Submit(
|
if _, err = contract.Submit(
|
||||||
"PurgeAsset",
|
"PurgeAsset",
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -27,8 +27,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fabric-contract-api": "~2.5",
|
"fabric-contract-api": "~2.5",
|
||||||
"fabric-shim": "~2.5",
|
"fabric-shim": "~2.5",
|
||||||
"json-stringify-deterministic": "^1.0.0",
|
"json-stringify-deterministic": "^1.0.12",
|
||||||
"sort-keys-recursive": "^2.1.0"
|
"sort-keys-recursive": "^2.1.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.19.33",
|
"@types/node": "^18.19.33",
|
||||||
|
|
|
||||||
|
|
@ -2,41 +2,52 @@
|
||||||
SPDX-License-Identifier: Apache-2.0
|
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";
|
import { nonEmptyString, positiveNumber } from './utils';
|
||||||
|
|
||||||
@Object()
|
@Object()
|
||||||
// Asset describes main asset details that are visible to all organizations
|
// Asset describes main asset details that are visible to all organizations
|
||||||
export class Asset {
|
export class Asset {
|
||||||
@Property()
|
@Property()
|
||||||
docType?: string;
|
objectType?: string;
|
||||||
|
|
||||||
@Property()
|
@Property()
|
||||||
ID: string = "";
|
ID: string = '';
|
||||||
|
|
||||||
@Property()
|
@Property()
|
||||||
Color: string = "";
|
Color: string = '';
|
||||||
|
|
||||||
@Property()
|
@Property()
|
||||||
Size: number = 0;
|
Size: number = 0;
|
||||||
|
|
||||||
@Property()
|
@Property()
|
||||||
Owner: string = "";
|
Owner: string = '';
|
||||||
|
|
||||||
static fromBytes(bytes: Uint8Array): Asset {
|
static fromBytes(bytes: Uint8Array): Asset {
|
||||||
if (bytes.length === 0) {
|
if (bytes.length === 0) {
|
||||||
throw new Error("no asset data");
|
throw new Error('no asset data');
|
||||||
}
|
}
|
||||||
const json = Buffer.from(bytes).toString();
|
const json = Buffer.from(bytes).toString('utf8');
|
||||||
const properties = JSON.parse(json) as Partial<Asset>;
|
const properties = JSON.parse(json) as Partial<Asset>;
|
||||||
|
|
||||||
const result = new Asset();
|
return {
|
||||||
result.docType = properties.docType;
|
objectType: properties.objectType,
|
||||||
result.ID = nonEmptyString(properties.ID, "ID field must be a non-empty string");
|
ID: nonEmptyString(
|
||||||
result.Color = nonEmptyString(properties.Color, "Color field must be a non-empty string");
|
properties.ID,
|
||||||
result.Size = positiveNumber(properties.Size, "Size field must be a positive integer");
|
'ID field must be a non-empty string'
|
||||||
result.Owner = nonEmptyString(properties.Owner, "appraiseOwner field must be a non-empty string");
|
),
|
||||||
|
Color: nonEmptyString(
|
||||||
return result;
|
properties.Color,
|
||||||
|
'Color field must be a non-empty string'
|
||||||
|
),
|
||||||
|
Size: positiveNumber(
|
||||||
|
properties.Size,
|
||||||
|
'Size field must be a positive integer'
|
||||||
|
),
|
||||||
|
Owner: nonEmptyString(
|
||||||
|
properties.Owner,
|
||||||
|
'appraiseOwner field must be a non-empty string'
|
||||||
|
),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,36 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
import { Context, Contract, Info, Transaction } from 'fabric-contract-api';
|
import {
|
||||||
|
Context,
|
||||||
|
Contract,
|
||||||
|
Info,
|
||||||
|
Returns,
|
||||||
|
Transaction,
|
||||||
|
} from 'fabric-contract-api';
|
||||||
import stringify from 'json-stringify-deterministic';
|
import stringify from 'json-stringify-deterministic';
|
||||||
import sortKeysRecursive from 'sort-keys-recursive';
|
import sortKeysRecursive from 'sort-keys-recursive';
|
||||||
import { Asset } from './asset';
|
import { Asset } from './asset';
|
||||||
import { AssetPrivateDetails } from './assetTransferDetails';
|
import { AssetPrivateDetails } from './assetTransferDetails';
|
||||||
import { TransientAssetDelete, TransientAssetOwner, TransientAssetProperties, TransientAssetPurge, TransientAssetValue } from './assetTransferTransientInput';
|
import {
|
||||||
|
readTransientAssetOwner,
|
||||||
|
readTransientAssetValue,
|
||||||
|
TransientAssetDelete,
|
||||||
|
TransientAssetProperties,
|
||||||
|
TransientAssetPurge,
|
||||||
|
} from './assetTransferTransientInput';
|
||||||
import { TransferAgreement } from './transferAgreement';
|
import { TransferAgreement } from './transferAgreement';
|
||||||
|
|
||||||
const assetCollection = 'assetCollection';
|
const assetCollection = 'assetCollection';
|
||||||
const transferAgreementObjectType = 'transferAgreement';
|
const transferAgreementObjectType = 'transferAgreement';
|
||||||
|
|
||||||
@Info({ title: 'AssetTransfer', description: 'Smart contract for trading assets' })
|
const utf8Encoder = new TextEncoder();
|
||||||
export class AssetTransfer extends Contract {
|
|
||||||
|
|
||||||
|
@Info({
|
||||||
|
title: 'AssetTransfer',
|
||||||
|
description: 'Smart contract for trading assets',
|
||||||
|
})
|
||||||
|
export class AssetTransfer extends Contract {
|
||||||
// CreateAsset issues a new asset to the world state with given details.
|
// CreateAsset issues a new asset to the world state with given details.
|
||||||
@Transaction()
|
@Transaction()
|
||||||
public async CreateAsset(ctx: Context): Promise<void> {
|
public async CreateAsset(ctx: Context): Promise<void> {
|
||||||
|
|
@ -22,9 +38,14 @@ export class AssetTransfer extends Contract {
|
||||||
const assetProperties = new TransientAssetProperties(transientMap);
|
const assetProperties = new TransientAssetProperties(transientMap);
|
||||||
|
|
||||||
// Check if asset already exists
|
// Check if asset already exists
|
||||||
const assetAsBytes = await ctx.stub.getPrivateData(assetCollection, assetProperties.assetID);
|
const assetAsBytes = await ctx.stub.getPrivateData(
|
||||||
|
assetCollection,
|
||||||
|
assetProperties.assetID
|
||||||
|
);
|
||||||
if (assetAsBytes.length !== 0) {
|
if (assetAsBytes.length !== 0) {
|
||||||
throw new Error('this asset already exists: ' + assetProperties.assetID);
|
throw new Error(
|
||||||
|
'this asset already exists: ' + assetProperties.assetID
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get ID of submitting client identity
|
// Get ID of submitting client identity
|
||||||
|
|
@ -45,7 +66,11 @@ export class AssetTransfer extends Contract {
|
||||||
// Save asset to private data collection
|
// Save asset to private data collection
|
||||||
// Typical logger, logs to stdout/file in the fabric managed docker container, running this chaincode
|
// Typical logger, logs to stdout/file in the fabric managed docker container, running this chaincode
|
||||||
// Look for container name like dev-peer0.org1.example.com-{chaincodename_version}-xyz
|
// Look for container name like dev-peer0.org1.example.com-{chaincodename_version}-xyz
|
||||||
await ctx.stub.putPrivateData(assetCollection, asset.ID, Buffer.from(stringify(sortKeysRecursive(asset))));
|
await ctx.stub.putPrivateData(
|
||||||
|
assetCollection,
|
||||||
|
asset.ID,
|
||||||
|
marshal(asset)
|
||||||
|
);
|
||||||
|
|
||||||
// Save asset details to collection visible to owning organization
|
// Save asset details to collection visible to owning organization
|
||||||
const assetPrivateDetails: AssetPrivateDetails = {
|
const assetPrivateDetails: AssetPrivateDetails = {
|
||||||
|
|
@ -55,8 +80,16 @@ export class AssetTransfer extends Contract {
|
||||||
// Get collection name for this organization.
|
// Get collection name for this organization.
|
||||||
const orgCollection = this.getCollectionName(ctx);
|
const orgCollection = this.getCollectionName(ctx);
|
||||||
// Put asset appraised value into owners org specific private data collection
|
// Put asset appraised value into owners org specific private data collection
|
||||||
console.log('Put: collection %v, ID %v', orgCollection, assetProperties.assetID);
|
console.log(
|
||||||
await ctx.stub.putPrivateData(orgCollection, asset.ID, Buffer.from(stringify(sortKeysRecursive(assetPrivateDetails))));
|
'Put: collection %v, ID %v',
|
||||||
|
orgCollection,
|
||||||
|
assetProperties.assetID
|
||||||
|
);
|
||||||
|
await ctx.stub.putPrivateData(
|
||||||
|
orgCollection,
|
||||||
|
asset.ID,
|
||||||
|
marshal(assetPrivateDetails)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// AgreeToTransfer is used by the potential buyer of the asset to agree to the
|
// AgreeToTransfer is used by the potential buyer of the asset to agree to the
|
||||||
|
|
@ -69,7 +102,7 @@ export class AssetTransfer extends Contract {
|
||||||
const clientID = ctx.clientIdentity.getID();
|
const clientID = ctx.clientIdentity.getID();
|
||||||
// Value is private, therefore it gets passed in transient field
|
// Value is private, therefore it gets passed in transient field
|
||||||
const transientMap = ctx.stub.getTransient();
|
const transientMap = ctx.stub.getTransient();
|
||||||
const assetValue = new TransientAssetValue(transientMap);
|
const assetValue = readTransientAssetValue(transientMap);
|
||||||
|
|
||||||
const valueJSON: AssetPrivateDetails = {
|
const valueJSON: AssetPrivateDetails = {
|
||||||
ID: assetValue.assetID,
|
ID: assetValue.assetID,
|
||||||
|
|
@ -77,20 +110,38 @@ export class AssetTransfer extends Contract {
|
||||||
};
|
};
|
||||||
// Read asset from the private data collection
|
// Read asset from the private data collection
|
||||||
const asset = await this.ReadAsset(ctx, valueJSON.ID);
|
const asset = await this.ReadAsset(ctx, valueJSON.ID);
|
||||||
|
if (!asset) {
|
||||||
|
throw new Error(`${valueJSON.ID} does not exist`);
|
||||||
|
}
|
||||||
// Verify that the client is submitting request to peer in their organization
|
// Verify that the client is submitting request to peer in their organization
|
||||||
this.verifyClientOrgMatchesPeerOrg(ctx);
|
this.verifyClientOrgMatchesPeerOrg(ctx);
|
||||||
|
|
||||||
// Get collection name for this organization. Needs to be read by a member of the organization.
|
// Get collection name for this organization. Needs to be read by a member of the organization.
|
||||||
const orgCollection = this.getCollectionName(ctx);
|
const orgCollection = this.getCollectionName(ctx);
|
||||||
console.log(`AgreeToTransfer Put: collection ${orgCollection}, ID ${valueJSON.ID}`);
|
console.log(
|
||||||
|
`AgreeToTransfer Put: collection ${orgCollection}, ID ${valueJSON.ID}`
|
||||||
|
);
|
||||||
// Put agreed value in the org specifc private data collection
|
// Put agreed value in the org specifc private data collection
|
||||||
await ctx.stub.putPrivateData(orgCollection, asset.ID, Buffer.from(stringify(sortKeysRecursive(valueJSON))));
|
await ctx.stub.putPrivateData(
|
||||||
|
orgCollection,
|
||||||
|
asset.ID,
|
||||||
|
marshal(valueJSON)
|
||||||
|
);
|
||||||
// Create agreeement that indicates which identity has agreed to purchase
|
// Create agreeement that indicates which identity has agreed to purchase
|
||||||
// In a more realistic transfer scenario, a transfer agreement would be secured to ensure that it cannot
|
// In a more realistic transfer scenario, a transfer agreement would be secured to ensure that it cannot
|
||||||
// be overwritten by another channel member
|
// be overwritten by another channel member
|
||||||
const transferAgreeKey = ctx.stub.createCompositeKey(transferAgreementObjectType, [valueJSON.ID]);
|
const transferAgreeKey = ctx.stub.createCompositeKey(
|
||||||
console.log(`AgreeToTransfer Put: collection ${assetCollection}, ID ${valueJSON.ID}, Key ${transferAgreeKey}`);
|
transferAgreementObjectType,
|
||||||
await ctx.stub.putPrivateData(assetCollection, transferAgreeKey, new Uint8Array(Buffer.from(clientID)));
|
[valueJSON.ID]
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`AgreeToTransfer Put: collection ${assetCollection}, ID ${valueJSON.ID}, Key ${transferAgreeKey}`
|
||||||
|
);
|
||||||
|
await ctx.stub.putPrivateData(
|
||||||
|
assetCollection,
|
||||||
|
transferAgreeKey,
|
||||||
|
utf8Encoder.encode(clientID)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transaction()
|
@Transaction()
|
||||||
|
|
@ -98,31 +149,57 @@ export class AssetTransfer extends Contract {
|
||||||
public async TransferAsset(ctx: Context): Promise<void> {
|
public async TransferAsset(ctx: Context): Promise<void> {
|
||||||
// Asset properties are private, therefore they get passed in transient field
|
// Asset properties are private, therefore they get passed in transient field
|
||||||
const transientMap = ctx.stub.getTransient();
|
const transientMap = ctx.stub.getTransient();
|
||||||
const assetOwner = new TransientAssetOwner(transientMap);
|
const assetOwner = readTransientAssetOwner(transientMap);
|
||||||
|
|
||||||
console.log('TransferAsset: verify asset exists ID ' + assetOwner.assetID);
|
console.log(
|
||||||
|
'TransferAsset: verify asset exists ID ' + assetOwner.assetID
|
||||||
|
);
|
||||||
// Read asset from the private data collection
|
// Read asset from the private data collection
|
||||||
const asset = await this.ReadAsset(ctx, assetOwner.assetID);
|
const asset = await this.ReadAsset(ctx, assetOwner.assetID);
|
||||||
|
if (!asset) {
|
||||||
|
throw new Error(`${assetOwner.assetID} does not exist`);
|
||||||
|
}
|
||||||
// Verify that the client is submitting request to peer in their organization
|
// Verify that the client is submitting request to peer in their organization
|
||||||
this.verifyClientOrgMatchesPeerOrg(ctx);
|
this.verifyClientOrgMatchesPeerOrg(ctx);
|
||||||
// Verify transfer details and transfer owner
|
// Verify transfer details and transfer owner
|
||||||
await this.verifyAgreement(ctx, assetOwner.assetID, asset.Owner, assetOwner.buyerMSP);
|
await this.verifyAgreement(
|
||||||
|
ctx,
|
||||||
|
assetOwner.assetID,
|
||||||
|
asset.Owner,
|
||||||
|
assetOwner.buyerMSP
|
||||||
|
);
|
||||||
|
|
||||||
|
const transferAgreement = await this.ReadTransferAgreement(
|
||||||
|
ctx,
|
||||||
|
assetOwner.assetID
|
||||||
|
);
|
||||||
|
|
||||||
const transferAgreement = await this.ReadTransferAgreement(ctx, assetOwner.assetID);
|
|
||||||
if (transferAgreement.BuyerID === '') {
|
if (transferAgreement.BuyerID === '') {
|
||||||
throw new Error('BuyerID not found in TransferAgreement for ' + assetOwner.assetID);
|
throw new Error(
|
||||||
|
'BuyerID not found in TransferAgreement for ' +
|
||||||
|
assetOwner.assetID
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// Transfer asset in private data collection to new owner
|
// Transfer asset in private data collection to new owner
|
||||||
asset.Owner = transferAgreement.BuyerID;
|
asset.Owner = transferAgreement.BuyerID;
|
||||||
console.log(`TransferAsset Put: collection ${assetCollection}, ID ${assetOwner.assetID}`);
|
console.log(
|
||||||
await ctx.stub.putPrivateData(assetCollection, assetOwner.assetID, Buffer.from(stringify(sortKeysRecursive(asset)))); // rewrite the asset
|
`TransferAsset Put: collection ${assetCollection}, ID ${assetOwner.assetID}`
|
||||||
|
);
|
||||||
|
await ctx.stub.putPrivateData(
|
||||||
|
assetCollection,
|
||||||
|
assetOwner.assetID,
|
||||||
|
marshal(asset)
|
||||||
|
); // rewrite the asset
|
||||||
|
|
||||||
// Get collection name for this organization
|
// Get collection name for this organization
|
||||||
const ownersCollection = this.getCollectionName(ctx);
|
const ownersCollection = this.getCollectionName(ctx);
|
||||||
// Delete the asset appraised value from this organization's private data collection
|
// Delete the asset appraised value from this organization's private data collection
|
||||||
await ctx.stub.deletePrivateData(ownersCollection, assetOwner.assetID);
|
await ctx.stub.deletePrivateData(ownersCollection, assetOwner.assetID);
|
||||||
// Delete the transfer agreement from the asset collection
|
// Delete the transfer agreement from the asset collection
|
||||||
const transferAgreeKey = ctx.stub.createCompositeKey(transferAgreementObjectType, [assetOwner.assetID]);
|
const transferAgreeKey = ctx.stub.createCompositeKey(
|
||||||
|
transferAgreementObjectType,
|
||||||
|
[assetOwner.assetID]
|
||||||
|
);
|
||||||
await ctx.stub.deletePrivateData(assetCollection, transferAgreeKey);
|
await ctx.stub.deletePrivateData(assetCollection, transferAgreeKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -138,15 +215,23 @@ export class AssetTransfer extends Contract {
|
||||||
|
|
||||||
console.log('Deleting Asset: ' + assetDelete.assetID);
|
console.log('Deleting Asset: ' + assetDelete.assetID);
|
||||||
// get the asset from chaincode state
|
// get the asset from chaincode state
|
||||||
const valAsbytes = await ctx.stub.getPrivateData(assetCollection, assetDelete.assetID);
|
const valAsbytes = await ctx.stub.getPrivateData(
|
||||||
|
assetCollection,
|
||||||
|
assetDelete.assetID
|
||||||
|
);
|
||||||
if (valAsbytes.length === 0) {
|
if (valAsbytes.length === 0) {
|
||||||
throw new Error('asset not found: ' + assetDelete.assetID);
|
throw new Error('asset not found: ' + assetDelete.assetID);
|
||||||
}
|
}
|
||||||
const ownerCollection = this.getCollectionName(ctx);
|
const ownerCollection = this.getCollectionName(ctx);
|
||||||
// Check the asset is in the caller org's private collection
|
// Check the asset is in the caller org's private collection
|
||||||
const valAsbytesPrivate = await ctx.stub.getPrivateData(ownerCollection, assetDelete.assetID);
|
const valAsbytesPrivate = await ctx.stub.getPrivateData(
|
||||||
|
ownerCollection,
|
||||||
|
assetDelete.assetID
|
||||||
|
);
|
||||||
if (valAsbytesPrivate.length === 0) {
|
if (valAsbytesPrivate.length === 0) {
|
||||||
throw new Error(`asset not found in owner's private Collection: ${ownerCollection} : ${assetDelete.assetID}`);
|
throw new Error(
|
||||||
|
`asset not found in owner's private Collection: ${ownerCollection} : ${assetDelete.assetID}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// delete the asset from state
|
// delete the asset from state
|
||||||
await ctx.stub.deletePrivateData(assetCollection, assetDelete.assetID);
|
await ctx.stub.deletePrivateData(assetCollection, assetDelete.assetID);
|
||||||
|
|
@ -191,15 +276,26 @@ export class AssetTransfer extends Contract {
|
||||||
// Get proposers collection.
|
// Get proposers collection.
|
||||||
const orgCollection = this.getCollectionName(ctx);
|
const orgCollection = this.getCollectionName(ctx);
|
||||||
// Delete the transfer agreement from the asset collection
|
// Delete the transfer agreement from the asset collection
|
||||||
const transferAgreeKey = ctx.stub.createCompositeKey(transferAgreementObjectType, [agreementDelete.assetID]);
|
const transferAgreeKey = ctx.stub.createCompositeKey(
|
||||||
|
transferAgreementObjectType,
|
||||||
|
[agreementDelete.assetID]
|
||||||
|
);
|
||||||
// get the transfer_agreement
|
// get the transfer_agreement
|
||||||
const valAsbytes = await ctx.stub.getPrivateData(assetCollection, transferAgreeKey);
|
const valAsbytes = await ctx.stub.getPrivateData(
|
||||||
|
assetCollection,
|
||||||
|
transferAgreeKey
|
||||||
|
);
|
||||||
|
|
||||||
if (valAsbytes.length === 0) {
|
if (valAsbytes.length === 0) {
|
||||||
throw new Error(`asset's transfer_agreement does not exist: ${agreementDelete.assetID}`);
|
throw new Error(
|
||||||
|
`asset's transfer_agreement does not exist: ${agreementDelete.assetID}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// Delete the asset
|
// Delete the asset
|
||||||
await ctx.stub.deletePrivateData(orgCollection, agreementDelete.assetID);
|
await ctx.stub.deletePrivateData(
|
||||||
|
orgCollection,
|
||||||
|
agreementDelete.assetID
|
||||||
|
);
|
||||||
// Delete transfer agreement record, remove agreement from state
|
// Delete transfer agreement record, remove agreement from state
|
||||||
await ctx.stub.deletePrivateData(assetCollection, transferAgreeKey);
|
await ctx.stub.deletePrivateData(assetCollection, transferAgreeKey);
|
||||||
}
|
}
|
||||||
|
|
@ -209,36 +305,59 @@ export class AssetTransfer extends Contract {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// ReadAsset reads the information from collection
|
// ReadAsset reads the information from collection
|
||||||
@Transaction()
|
@Transaction(false)
|
||||||
public async ReadAsset(ctx: Context, id: string): Promise<Asset> {
|
@Returns('Asset')
|
||||||
|
public async ReadAsset(
|
||||||
|
ctx: Context,
|
||||||
|
id: string
|
||||||
|
): Promise<Asset | undefined> {
|
||||||
// Check if asset already exists
|
// Check if asset already exists
|
||||||
const assetAsBytes = await ctx.stub.getPrivateData(assetCollection, id);
|
const assetAsBytes = await ctx.stub.getPrivateData(assetCollection, id);
|
||||||
// No Asset found, return empty response
|
// No Asset found, return empty response
|
||||||
if (assetAsBytes.length === 0) {
|
if (assetAsBytes.length === 0) {
|
||||||
throw new Error(id + ' does not exist in collection ' + assetCollection);
|
console.log(
|
||||||
|
id + ' does not exist in collection ' + assetCollection
|
||||||
|
);
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
return Asset.fromBytes(assetAsBytes);
|
return Asset.fromBytes(assetAsBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadAssetPrivateDetails reads the asset private details in organization specific collection
|
// ReadAssetPrivateDetails reads the asset private details in organization specific collection
|
||||||
@Transaction()
|
@Transaction(false)
|
||||||
public async ReadAssetPrivateDetails(ctx: Context, collection: string, id: string): Promise<AssetPrivateDetails> {
|
@Returns('AssetPrivateDetails')
|
||||||
|
public async ReadAssetPrivateDetails(
|
||||||
|
ctx: Context,
|
||||||
|
collection: string,
|
||||||
|
id: string
|
||||||
|
): Promise<AssetPrivateDetails | undefined> {
|
||||||
// Check if asset already exists
|
// Check if asset already exists
|
||||||
const detailBytes = await ctx.stub.getPrivateData(collection, id);
|
const detailBytes = await ctx.stub.getPrivateData(collection, id);
|
||||||
// No Asset found, return empty response
|
// No Asset found, return empty response
|
||||||
if (detailBytes.length === 0) {
|
if (detailBytes.length === 0) {
|
||||||
throw new Error(id + ' does not exist in collection ' + collection);
|
console.log(id + ' does not exist in collection ' + collection);
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
return AssetPrivateDetails.fromBytes(detailBytes);
|
return AssetPrivateDetails.fromBytes(detailBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadTransferAgreement gets the buyer's identity from the transfer agreement from collection
|
// ReadTransferAgreement gets the buyer's identity from the transfer agreement from collection
|
||||||
@Transaction()
|
@Transaction(false)
|
||||||
public async ReadTransferAgreement(ctx: Context, assetID: string): Promise<TransferAgreement> {
|
@Returns('TransferAgreement')
|
||||||
|
public async ReadTransferAgreement(
|
||||||
|
ctx: Context,
|
||||||
|
assetID: string
|
||||||
|
): Promise<TransferAgreement> {
|
||||||
// composite key for TransferAgreement of this asset
|
// composite key for TransferAgreement of this asset
|
||||||
const transferAgreeKey = ctx.stub.createCompositeKey(transferAgreementObjectType, [assetID]);
|
const transferAgreeKey = ctx.stub.createCompositeKey(
|
||||||
|
transferAgreementObjectType,
|
||||||
|
[assetID]
|
||||||
|
);
|
||||||
// Get the identity from collection
|
// Get the identity from collection
|
||||||
const buyerIdentity = await ctx.stub.getPrivateData(assetCollection, transferAgreeKey);
|
const buyerIdentity = await ctx.stub.getPrivateData(
|
||||||
|
assetCollection,
|
||||||
|
transferAgreeKey
|
||||||
|
);
|
||||||
|
|
||||||
if (buyerIdentity.length === 0) {
|
if (buyerIdentity.length === 0) {
|
||||||
throw new Error(`TransferAgreement for ${assetID} does not exist `);
|
throw new Error(`TransferAgreement for ${assetID} does not exist `);
|
||||||
|
|
@ -253,9 +372,18 @@ export class AssetTransfer extends Contract {
|
||||||
// GetAssetByRange performs a range query based on the start and end keys provided. Range
|
// GetAssetByRange performs a range query based on the start and end keys provided. Range
|
||||||
// queries can be used to read data from private data collections, but can not be used in
|
// queries can be used to read data from private data collections, but can not be used in
|
||||||
// a transaction that also writes to private data.
|
// a transaction that also writes to private data.
|
||||||
@Transaction()
|
@Transaction(false)
|
||||||
public async GetAssetByRange(ctx: Context, startKey: string, endKey: string): Promise<Asset[]> {
|
@Returns('Asset[]')
|
||||||
const resultsIterator = ctx.stub.getPrivateDataByRange(assetCollection, startKey, endKey);
|
public async GetAssetByRange(
|
||||||
|
ctx: Context,
|
||||||
|
startKey: string,
|
||||||
|
endKey: string
|
||||||
|
): Promise<Asset[]> {
|
||||||
|
const resultsIterator = ctx.stub.getPrivateDataByRange(
|
||||||
|
assetCollection,
|
||||||
|
startKey,
|
||||||
|
endKey
|
||||||
|
);
|
||||||
const results: Asset[] = [];
|
const results: Asset[] = [];
|
||||||
for await (const res of resultsIterator) {
|
for await (const res of resultsIterator) {
|
||||||
const asset = Asset.fromBytes(res.value);
|
const asset = Asset.fromBytes(res.value);
|
||||||
|
|
@ -269,12 +397,19 @@ export class AssetTransfer extends Contract {
|
||||||
// verifyAgreement is an internal helper function used by TransferAsset to verify
|
// 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
|
// that the transfer is being initiated by the owner and that the buyer has agreed
|
||||||
// to the same appraisal value as the owner
|
// 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
|
// Check 1: verify that the transfer is being initiatied by the owner
|
||||||
// Get ID of submitting client identity
|
// Get ID of submitting client identity
|
||||||
const clientID = ctx.clientIdentity.getID();
|
const clientID = ctx.clientIdentity.getID();
|
||||||
if (clientID !== owner) {
|
if (clientID !== owner) {
|
||||||
throw new Error(`error: submitting client(${clientID}) identity does not own asset ${assetID}.Owner is ${owner}`);
|
throw new Error(
|
||||||
|
`error: submitting client(${clientID}) identity does not own asset ${assetID}.Owner is ${owner}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// Check 2: verify that the buyer has agreed to the appraised value
|
// Check 2: verify that the buyer has agreed to the appraised value
|
||||||
// Get collection names
|
// Get collection names
|
||||||
|
|
@ -282,19 +417,38 @@ export class AssetTransfer extends Contract {
|
||||||
|
|
||||||
const collectionBuyer = buyerMSP + 'PrivateCollection'; // get buyers collection
|
const collectionBuyer = buyerMSP + 'PrivateCollection'; // get buyers collection
|
||||||
// Get hash of owners agreed to value
|
// Get hash of owners agreed to value
|
||||||
const ownerAppraisedValueHash = await ctx.stub.getPrivateDataHash(collectionOwner, assetID);
|
const ownerAppraisedValueHash = await ctx.stub.getPrivateDataHash(
|
||||||
|
collectionOwner,
|
||||||
|
assetID
|
||||||
|
);
|
||||||
|
|
||||||
if (ownerAppraisedValueHash.length === 0) {
|
if (ownerAppraisedValueHash.length === 0) {
|
||||||
throw new Error(`hash of appraised value for ${assetID} does not exist in collection ${collectionOwner}`);
|
throw new Error(
|
||||||
|
`hash of appraised value for ${assetID} does not exist in collection ${collectionOwner}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// Get hash of buyers agreed to value
|
// Get hash of buyers agreed to value
|
||||||
const buyerAppraisedValueHash = await ctx.stub.getPrivateDataHash(collectionBuyer, assetID);
|
const buyerAppraisedValueHash = await ctx.stub.getPrivateDataHash(
|
||||||
|
collectionBuyer,
|
||||||
|
assetID
|
||||||
|
);
|
||||||
if (buyerAppraisedValueHash.length === 0) {
|
if (buyerAppraisedValueHash.length === 0) {
|
||||||
throw new Error(`hash of appraised value for ${assetID} does not exist in collection ${collectionBuyer}. 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
|
// Verify that the two hashes match
|
||||||
if (ownerAppraisedValueHash.toString() !== buyerAppraisedValueHash.toString()) {
|
const ownerValueHashHex = Buffer.from(ownerAppraisedValueHash).toString(
|
||||||
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')}`);
|
'hex'
|
||||||
|
);
|
||||||
|
const buyerValueHashHex = Buffer.from(buyerAppraisedValueHash).toString(
|
||||||
|
'hex'
|
||||||
|
);
|
||||||
|
if (ownerValueHashHex !== buyerValueHashHex) {
|
||||||
|
throw new Error(
|
||||||
|
`hash for appraised value for owner ${ownerValueHashHex} does not match value for buyer ${buyerValueHashHex}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// getCollectionName is an internal helper function to get collection of submitting client identity.
|
// getCollectionName is an internal helper function to get collection of submitting client identity.
|
||||||
|
|
@ -308,7 +462,6 @@ export class AssetTransfer extends Contract {
|
||||||
}
|
}
|
||||||
// Get ID of submitting client identity
|
// Get ID of submitting client identity
|
||||||
public submittingClientIdentity(ctx: Context): string {
|
public submittingClientIdentity(ctx: Context): string {
|
||||||
|
|
||||||
const b64ID = ctx.clientIdentity.getID();
|
const b64ID = ctx.clientIdentity.getID();
|
||||||
|
|
||||||
// base64.StdEncoding.DecodeString(b64ID);
|
// base64.StdEncoding.DecodeString(b64ID);
|
||||||
|
|
@ -318,13 +471,17 @@ export class AssetTransfer extends Contract {
|
||||||
}
|
}
|
||||||
// verifyClientOrgMatchesPeerOrg is an internal function used verify client org id and matches peer org id.
|
// verifyClientOrgMatchesPeerOrg is an internal function used verify client org id and matches peer org id.
|
||||||
public verifyClientOrgMatchesPeerOrg(ctx: Context): void {
|
public verifyClientOrgMatchesPeerOrg(ctx: Context): void {
|
||||||
|
|
||||||
const clientMSPID = ctx.clientIdentity.getMSPID();
|
const clientMSPID = ctx.clientIdentity.getMSPID();
|
||||||
|
|
||||||
const peerMSPID = ctx.stub.getMspID();
|
const peerMSPID = ctx.stub.getMspID();
|
||||||
|
|
||||||
if (clientMSPID !== peerMSPID) {
|
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);
|
throw new Error(
|
||||||
|
'client from org %v is not authorized to read or write private data from an org ' +
|
||||||
|
clientMSPID +
|
||||||
|
' peer ' +
|
||||||
|
peerMSPID
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// =======Rich queries =========================================================================
|
// =======Rich queries =========================================================================
|
||||||
|
|
@ -347,8 +504,11 @@ export class AssetTransfer extends Contract {
|
||||||
// and accepting a single query parameter (owner).
|
// and accepting a single query parameter (owner).
|
||||||
// Only available on state databases that support rich query (e.g. CouchDB)
|
// Only available on state databases that support rich query (e.g. CouchDB)
|
||||||
// =========================================================================================
|
// =========================================================================================
|
||||||
public async QueryAssetByOwner(ctx: Context, assetType: string, owner: string): Promise<Asset[]> {
|
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);
|
return await this.getQueryResultForQueryString(ctx, queryString);
|
||||||
|
|
@ -358,9 +518,14 @@ export class AssetTransfer extends Contract {
|
||||||
return this.getQueryResultForQueryString(ctx, queryString);
|
return this.getQueryResultForQueryString(ctx, queryString);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getQueryResultForQueryString(ctx: Context, queryString: string): Promise<Asset[]> {
|
public async getQueryResultForQueryString(
|
||||||
|
ctx: Context,
|
||||||
const resultsIterator = ctx.stub.getPrivateDataQueryResult(assetCollection, queryString);
|
queryString: string
|
||||||
|
): Promise<Asset[]> {
|
||||||
|
const resultsIterator = ctx.stub.getPrivateDataQueryResult(
|
||||||
|
assetCollection,
|
||||||
|
queryString
|
||||||
|
);
|
||||||
|
|
||||||
const results: Asset[] = [];
|
const results: Asset[] = [];
|
||||||
|
|
||||||
|
|
@ -372,3 +537,11 @@ export class AssetTransfer extends Contract {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function marshal(o: object): Uint8Array {
|
||||||
|
return utf8Encoder.encode(toJSON(o));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toJSON(o: object): string {
|
||||||
|
return stringify(sortKeysRecursive(o));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { nonEmptyString, positiveNumber } from "./utils";
|
import { nonEmptyString, positiveNumber } from './utils';
|
||||||
|
|
||||||
export class TransientAssetProperties {
|
export class TransientAssetProperties {
|
||||||
objectType: string;
|
objectType: string;
|
||||||
|
|
@ -12,73 +12,105 @@ export class TransientAssetProperties {
|
||||||
appraisedValue: number;
|
appraisedValue: number;
|
||||||
|
|
||||||
constructor(transientMap: Map<string, Uint8Array>) {
|
constructor(transientMap: Map<string, Uint8Array>) {
|
||||||
const transient = transientMap.get("asset_properties");
|
const transient = transientMap.get('asset_properties');
|
||||||
if (!transient?.length) {
|
if (!transient?.length) {
|
||||||
throw new Error("no asset properties");
|
throw new Error('no asset properties');
|
||||||
}
|
}
|
||||||
const json = Buffer.from(transient).toString();
|
const json = Buffer.from(transient).toString();
|
||||||
const properties = JSON.parse(json) as Partial<TransientAssetProperties>;
|
const properties = JSON.parse(
|
||||||
|
json
|
||||||
|
) as Partial<TransientAssetProperties>;
|
||||||
|
|
||||||
this.objectType = nonEmptyString(properties.objectType, "objectType field must be a non-empty string");
|
this.objectType = nonEmptyString(
|
||||||
this.assetID = nonEmptyString(properties.assetID, "assetID field must be a non-empty string");
|
properties.objectType,
|
||||||
this.color = nonEmptyString(properties.color, "color field must be a non-empty string");
|
'objectType field must be a non-empty string'
|
||||||
this.size = positiveNumber(properties.size, "size field must be a positive integer");
|
);
|
||||||
|
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(
|
this.appraisedValue = positiveNumber(
|
||||||
properties.appraisedValue,
|
properties.appraisedValue,
|
||||||
"appraisedValue field must be a positive integer"
|
'appraisedValue field must be a positive integer'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TransientAssetValue {
|
export interface TransientAssetValue {
|
||||||
assetID: string;
|
assetID: string;
|
||||||
appraisedValue: number;
|
appraisedValue: number;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(transientMap: Map<string, Uint8Array>) {
|
export function readTransientAssetValue(
|
||||||
const transient = transientMap.get("asset_value");
|
transientMap: Map<string, Uint8Array>
|
||||||
|
): TransientAssetValue {
|
||||||
|
const transient = transientMap.get('asset_value');
|
||||||
if (!transient?.length) {
|
if (!transient?.length) {
|
||||||
throw new Error("no asset value");
|
throw new Error('no asset value');
|
||||||
}
|
}
|
||||||
const json = Buffer.from(transient).toString();
|
const json = Buffer.from(transient).toString();
|
||||||
const properties = JSON.parse(json) as Partial<TransientAssetValue>;
|
const properties = JSON.parse(json) as Partial<TransientAssetValue>;
|
||||||
|
|
||||||
this.assetID = nonEmptyString(properties.assetID, "assetID field must be a non-empty string");
|
const assetID = nonEmptyString(
|
||||||
this.appraisedValue = positiveNumber(
|
properties.assetID,
|
||||||
properties.appraisedValue,
|
'assetID field must be a non-empty string'
|
||||||
"appraisedValue field must be a positive integer"
|
|
||||||
);
|
);
|
||||||
}
|
const appraisedValue = positiveNumber(
|
||||||
|
properties.appraisedValue,
|
||||||
|
'appraisedValue field must be a positive integer'
|
||||||
|
);
|
||||||
|
|
||||||
|
return { assetID, appraisedValue };
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TransientAssetOwner {
|
export interface TransientAssetOwner {
|
||||||
assetID: string;
|
assetID: string;
|
||||||
buyerMSP: string;
|
buyerMSP: string;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(transientMap: Map<string, Uint8Array>) {
|
export function readTransientAssetOwner(transientMap: Map<string, Uint8Array>) {
|
||||||
const transient = transientMap.get("asset_owner");
|
const transient = transientMap.get('asset_owner');
|
||||||
if (!transient?.length) {
|
if (!transient?.length) {
|
||||||
throw new Error("no asset owner");
|
throw new Error('no asset owner');
|
||||||
}
|
}
|
||||||
const json = Buffer.from(transient).toString();
|
const json = Buffer.from(transient).toString();
|
||||||
const properties = JSON.parse(json) as Partial<TransientAssetOwner>;
|
const properties = JSON.parse(json) as Partial<TransientAssetOwner>;
|
||||||
|
|
||||||
this.assetID = nonEmptyString(properties.assetID, "assetID field must be a non-empty string");
|
const assetID = nonEmptyString(
|
||||||
this.buyerMSP = nonEmptyString(properties.buyerMSP, "buyerMSP field must be a non-empty string");
|
properties.assetID,
|
||||||
}
|
'assetID field must be a non-empty string'
|
||||||
|
);
|
||||||
|
const buyerMSP = nonEmptyString(
|
||||||
|
properties.buyerMSP,
|
||||||
|
'buyerMSP field must be a non-empty string'
|
||||||
|
);
|
||||||
|
|
||||||
|
return { assetID, buyerMSP };
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TransientAssetDelete {
|
export class TransientAssetDelete {
|
||||||
assetID: string;
|
assetID: string;
|
||||||
|
|
||||||
constructor(transientMap: Map<string, Uint8Array>) {
|
constructor(transientMap: Map<string, Uint8Array>) {
|
||||||
const transient = transientMap.get("asset_delete");
|
const transient = transientMap.get('asset_delete');
|
||||||
if (!transient?.length) {
|
if (!transient?.length) {
|
||||||
throw new Error("no asset delete");
|
throw new Error('no asset delete');
|
||||||
}
|
}
|
||||||
const json = Buffer.from(transient).toString();
|
const json = Buffer.from(transient).toString();
|
||||||
const properties = JSON.parse(json) as Partial<TransientAssetOwner>;
|
const properties = JSON.parse(json) as Partial<TransientAssetOwner>;
|
||||||
|
|
||||||
this.assetID = nonEmptyString(properties.assetID, "assetID field must be a non-empty string");
|
this.assetID = nonEmptyString(
|
||||||
|
properties.assetID,
|
||||||
|
'assetID field must be a non-empty string'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,15 +118,18 @@ export class TransientAssetPurge {
|
||||||
assetID: string;
|
assetID: string;
|
||||||
|
|
||||||
constructor(transientMap: Map<string, Uint8Array>) {
|
constructor(transientMap: Map<string, Uint8Array>) {
|
||||||
const transient = transientMap.get("asset_purge");
|
const transient = transientMap.get('asset_purge');
|
||||||
if (!transient?.length) {
|
if (!transient?.length) {
|
||||||
throw new Error("no asset purge");
|
throw new Error('no asset purge');
|
||||||
}
|
}
|
||||||
|
|
||||||
const json = Buffer.from(transient).toString();
|
const json = Buffer.from(transient).toString();
|
||||||
const properties = JSON.parse(json) as Partial<TransientAssetOwner>;
|
const properties = JSON.parse(json) as Partial<TransientAssetOwner>;
|
||||||
|
|
||||||
this.assetID = nonEmptyString(properties.assetID, "assetID field must be a non-empty string");
|
this.assetID = nonEmptyString(
|
||||||
|
properties.assetID,
|
||||||
|
'assetID field must be a non-empty string'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,14 +137,17 @@ export class TransientAgreementDelete {
|
||||||
assetID: string;
|
assetID: string;
|
||||||
|
|
||||||
constructor(transientMap: Map<string, Uint8Array>) {
|
constructor(transientMap: Map<string, Uint8Array>) {
|
||||||
const transient = transientMap.get("agreement_delete");
|
const transient = transientMap.get('agreement_delete');
|
||||||
if (!transient?.length) {
|
if (!transient?.length) {
|
||||||
throw new Error("no agreement delete");
|
throw new Error('no agreement delete');
|
||||||
}
|
}
|
||||||
|
|
||||||
const json = Buffer.from(transient).toString();
|
const json = Buffer.from(transient).toString();
|
||||||
const properties = JSON.parse(json) as Partial<TransientAssetOwner>;
|
const properties = JSON.parse(json) as Partial<TransientAssetOwner>;
|
||||||
|
|
||||||
this.assetID = nonEmptyString(properties.assetID, "assetID field must be a non-empty string");
|
this.assetID = nonEmptyString(
|
||||||
|
properties.assetID,
|
||||||
|
'assetID field must be a non-empty string'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue