From 454427dff69c9bd873083e4e395b1effaf7f5a1a Mon Sep 17 00:00:00 2001 From: Franck Chastagnol Date: Thu, 29 Nov 2018 22:48:20 -0800 Subject: [PATCH 1/8] checkpoint --- .../src/components/notification-message.js | 85 ++++++++++++------- .../contractInterface/marketplace/resolver.js | 19 +++++ .../marketplace/v00_adapter.js | 63 ++++++++++---- origin-js/src/models/notification.js | 7 ++ origin-js/src/resources/marketplace.js | 27 +++++- 5 files changed, 151 insertions(+), 50 deletions(-) diff --git a/origin-dapp/src/components/notification-message.js b/origin-dapp/src/components/notification-message.js index 580888ebcf25..64a1d94b8153 100644 --- a/origin-dapp/src/components/notification-message.js +++ b/origin-dapp/src/components/notification-message.js @@ -9,49 +9,72 @@ class NotificationMessage extends Component { constructor(props) { super(props) - this.intlMessages = defineMessages({ - offerMade: { - id: 'notification.offerMade', + const intlMessages = defineMessages({ + // + // Notifications received by the seller. + // + sellerOfferCreated: { + id: 'notification.sellerOfferCreated', defaultMessage: 'You have a new offer.' }, - offerAccepted: { - id: 'notification.purchaseSent', - defaultMessage: 'Your offer has been accepted.' + sellerOfferFinalized: { + id: 'notification.sellerOfferFinalized', + defaultMessage: 'Your transaction has been completed.' }, - saleConfirmed: { - id: 'notification.saleConfirmed', - defaultMessage: 'Your sale has been confirmed.' + sellerOfferDisputed: { + id: 'notification.sellerOfferDisputed', + defaultMessage: 'A problem has been reported with your transaction.' }, - sellerReviewed: { - id: 'notification.sellerReviewed', - defaultMessage: 'You have a new review.' + sellerOfferWithdrawn: { + id: 'notification.sellerOfferWithdrawn', + defaultMessage: 'An offer on your listing has been withdrawn.' + }, + sellerOfferRuling: { + id: 'notification.sellerOfferRuling', + defaultMessage: 'A ruling has been issued on your disputed transaction.' + }, + // + // Notifications received by the buyer. + // + buyerOfferAccepted: { + id: 'notification.buyerOfferAccepted', + defaultMessage: 'An offer you made has been accepted.' + }, + buyerOfferRuling: { + id: 'notification.buyerOfferRuling', + defaultMessage: 'A ruling has been issued on your disputed transaction.' + }, + buyerOfferReview: { + id: 'notification.buyerOfferReview', + defaultMessage: 'A review has been left on your transaction.' + }, + buyerOfferWithdrawn: { + id: 'notification.buyerOfferWithdrawn', + defaultMessage: 'An offer you made has been rejected.' } + }) + + this.notificationTypeToMessage = { + 'seller_offer_created': intlMessages.sellerOfferCreated, + 'seller_offer_finalized': intlMessages.sellerOfferFinalized, + 'seller_offer_disputed': intlMessages.sellerOfferDisputed, + 'seller_offer_ruling': intlMessages.sellerOfferRuling, + 'seller_offer_withdrawn': intlMessages.sellerOfferWithdrawn, + 'buyer_offer_accepted': intlMessages.buyerOfferAccepted, + 'buyer_offer_ruling': intlMessages.buyerOfferRuling, + 'buyer_offer_review': intlMessages.buyerOfferReview, + 'buyer_offer_withdrawn': intlMessages.buyerOfferWithdrawn, + } } render() { const { className, type } = this.props - let message - - switch (type) { - case 'buyer_review_received': - message = this.props.intl.formatMessage( - this.intlMessages.sellerReviewed - ) - break - case 'seller_review_received': - message = this.props.intl.formatMessage(this.intlMessages.saleConfirmed) - break - case 'buyer_listing_shipped': - message = this.props.intl.formatMessage(this.intlMessages.offerAccepted) - break - case 'seller_listing_purchased': - message = this.props.intl.formatMessage(this.intlMessages.offerMade) - break - default: + let message = this.notificationTypeToMessage(type) + if (!message) { return

{NON_PURCHASE_RELATED_MESSAGE}

} - + message = this.props.intl.formatMessage(message) return (
{message} diff --git a/origin-js/src/contractInterface/marketplace/resolver.js b/origin-js/src/contractInterface/marketplace/resolver.js index 055e96866824..80dc58db9213 100644 --- a/origin-js/src/contractInterface/marketplace/resolver.js +++ b/origin-js/src/contractInterface/marketplace/resolver.js @@ -263,6 +263,24 @@ export default class MarketplaceResolver { ) } + /** + * Returns all notifications relevant to a user. + * The notification status is set to 'read' if either + * - The notification was previously marked as read in local storage. + * - The event occurred prior to the subscription start time (e.g. date at which + * the notification component was initialized for the first time). + * + * @param {string} party - User's ETH address. + * @return {Promise} + **/ async getNotifications(party) { const network = await this.contractService.web3.eth.net.getId() let notifications = [] @@ -277,6 +295,7 @@ export default class MarketplaceResolver { version, transactionHash: notification.event.transactionHash }) + // Check if the event occurred prior to the subscription start time. const timestamp = await this.contractService.getTimestamp( notification.event ) diff --git a/origin-js/src/contractInterface/marketplace/v00_adapter.js b/origin-js/src/contractInterface/marketplace/v00_adapter.js index 8998b795da10..d1ef10a8296a 100644 --- a/origin-js/src/contractInterface/marketplace/v00_adapter.js +++ b/origin-js/src/contractInterface/marketplace/v00_adapter.js @@ -8,6 +8,19 @@ const OFFER_STATUS = [ 'withdrawn', 'ruling' ] +const offerStatusToSellerNotificationType = { + 'created': 'seller_offer_created', + 'finalized': 'seller_offer_finalized', + 'disputed': 'seller_offer_disputed', + 'ruling': 'seller_offer_ruling', + 'withdrawn': 'seller_offer_withdrawn', +} +const offerStatusToBuyerNotificationType = { + 'accepted': 'buyer_offer_accepted', + 'ruling': 'buyer_offer_ruling', + 'sellerReviewed': 'buyer_offer_review', + 'withdrawn': 'buyer_offer_withdrawn', +} const SUPPORTED_DEPOSIT_CURRENCIES = ['OGN'] const emptyAddress = '0x0000000000000000000000000000000000000000' @@ -233,8 +246,6 @@ class V00_MarkeplaceAdapter { await this.getContract() // Get the raw listing data from the contract. - // Note: once a listing is withdrawn, it is deleted from the blockchain to save - // on gas. In this cases rawListing is returned as an object with all its fields set to zero. const rawListing = await this.call('listings', [listingId]) // Find all events related to this listing @@ -253,9 +264,9 @@ class V00_MarkeplaceAdapter { if (event.event === 'ListingCreated') { ipfsHash = event.returnValues.ipfsHash } else if (event.event === 'ListingUpdated') { - // If a blockInfo is passed in, ignore udpated IPFS data that occurred after. + // If a blockInfo is passed in, ignore updated IPFS data that occurred after. // This is used when we want to see what a listing looked like at the time an offer was made. - // Specificatlly, on myPurchases and mySales requests as well as for arbitration. + // Specifically, on myPurchases and mySales requests as well as for arbitration. if (!blockInfo || (event.blockNumber < blockInfo.blockNumber) || (event.blockNumber === blockInfo.blockNumber && event.logIndex <= blockInfo.logIndex)) { @@ -273,6 +284,8 @@ class V00_MarkeplaceAdapter { offers[event.returnValues.offerID] = { status: 'ruling', event } } else if (event.event === 'OfferFinalized') { offers[event.returnValues.offerID] = { status: 'finalized', event } + } else if (event.event === 'OfferWithdrawn') { + offers[event.returnValues.offerID] = { status: 'withdrawn', event } } else if (event.event === 'OfferData') { offers[event.returnValues.offerID] = { status: 'sellerReviewed', event } } @@ -471,12 +484,13 @@ class V00_MarkeplaceAdapter { blockNumber = e.blockNumber logIndex = e.logIndex break - // In all cases below, the offer was deleted from the blochain + // In all cases below, the offer was deleted from the blockchain and therefore // rawOffer fields are set to zero => populate rawOffer.status based on event history. case 'OfferFinalized': rawOffer.status = 4 break - // TODO: Assumes OfferData event is a seller review + // FIXME: This assumes OfferData event is always a seller review whereas it may be + // emitted by the marketplace contract in other cases such as a seller initiated refund. case 'OfferData': rawOffer.status = 5 break @@ -507,6 +521,11 @@ class V00_MarkeplaceAdapter { return Object.assign({ timestamp }, transactionReceipt) } + /** + * Fetches all notifications for a user since inception. + * @param {string} party - User's ETH address. + * @return {Promise} + */ async getNotifications(party) { await this.getContract() @@ -515,11 +534,15 @@ class V00_MarkeplaceAdapter { const partyListingIds = [] const partyOfferIds = [] + // Fetch all marketplace events where user is the party. const events = await this.contract.getPastEvents('allEvents', { topics: [null, this.padTopic(party)], fromBlock: this.blockEpoch }) + // Create a list of + // - Ids of listings created by the user as a seller + // - Ids of offers made by the user as a buyer. for (const event of events) { if (event.event === 'ListingCreated') { partyListingIds.push(event.returnValues.listingID) @@ -532,35 +555,39 @@ class V00_MarkeplaceAdapter { } } - // Find pending offers and pending reviews + // Find events of interest on offers for listings created by the user as a seller. for (const listingId of partyListingIds) { const listing = await this.getListing(listingId) for (const offerId in listing.offers) { const offer = listing.offers[offerId] - if (offer.status === 'created') { - notifications.push({ - event: offer.event, - type: 'seller_listing_purchased', - resources: { listingId, offerId } - }) + // Skip the event if the action was initiated by the user. + if (party === offer.event.decoded.party) { + continue } - if (offer.status === 'finalized') { + const type = offerStatusToSellerNotificationType[offer.status] + if (type) { notifications.push({ + type, event: offer.event, - type: 'seller_review_received', resources: { listingId, offerId } }) } } } - // Find pending offers and pending reviews + + // Find events of interest on offers made by the user as a buyer. for (const [listingId, offerId] of partyOfferIds) { const listing = await this.getListing(listingId) const offer = listing.offers[offerId] - if (offer.status === 'accepted') { + // Skip the event if the action was initiated by the user. + if (party.toLowerCase() === offer.event.decoded.party.toLowerCase()) { + continue + } + const type = offerStatusToBuyerNotificationType[offer.status] + if (type) { notifications.push({ + type, event: offer.event, - type: 'buyer_listing_shipped', resources: { listingId, offerId } }) } diff --git a/origin-js/src/models/notification.js b/origin-js/src/models/notification.js index 4964e3955cd4..940bf97d134b 100644 --- a/origin-js/src/models/notification.js +++ b/origin-js/src/models/notification.js @@ -9,10 +9,17 @@ export const storeKeys = { export class Notification { constructor({ id, event, type, status, resources = {} } = {}) { + // Unique id with format --. this.id = id + // Web3 event. this.event = event + // See src/contractInterface/marketplace/v00_adapter.js for list of types. this.type = type + // 'read' or 'unread'. this.status = status + // Resources includes the following fields: + // - listing: Listing model object + // - purchase: Offer model object this.resources = resources } } diff --git a/origin-js/src/resources/marketplace.js b/origin-js/src/resources/marketplace.js index 2e7f63ba22ee..3af864831d53 100644 --- a/origin-js/src/resources/marketplace.js +++ b/origin-js/src/resources/marketplace.js @@ -461,6 +461,8 @@ export default class Marketplace { /** * Withdraws an offer. + * This may be called by either the buyer (to cancel an offer) + * or the seller (to reject an offer). * @param {string} id - Offer unique ID. * @param ipfsData - Data to store in IPFS. For future use, currently empty. * @param {func(confirmationCount, transactionReceipt)} confirmationCallback @@ -617,10 +619,23 @@ export default class Marketplace { return reviews } + /** + * Fetch all notifications for the current user. + * + * Note: The current implementation won't scale well, especially for sellers with large + * number of listings/offers. When this becomes an issue, the logic could be optimized. + * For example an possibility would be to add a "fromBlockNumber" argument to allow to fetch + * incrementally new notifications. Alternatively, support for a "performance mode" that + * fetches data from the back-end could be added. + * + * @return {Promise} + */ async getNotifications() { + // Fetch all notifications. const party = await this.contractService.currentAccount() const notifications = await this.resolver.getNotifications(party) - let isValid = true + + // Decorate each notification with listing and offer data. const withResources = await Promise.all(notifications.map(async (notification) => { if (notification.resources.listingId) { notification.resources.listing = await this.getListing( @@ -631,6 +646,7 @@ export default class Marketplace { }) ) } + let isValid = true if (notification.resources.offerId) { let offer try { @@ -643,8 +659,11 @@ export default class Marketplace { }) ) } catch(e) { + // TBD: what exactly are we guarding against here ? + // Under which condition would fetching an offer raise an exception ? isValid = false } + // FIXME: this should be renamed from purchase to offer. notification.resources.purchase = offer } return isValid ? new Notification(notification) : null @@ -652,6 +671,12 @@ export default class Marketplace { return withResources.filter(notification => notification !== null) } + /** + * Update the status for a notification in the local store. + * @param {string} id - Unique notification ID + * @param {string} status - 'read' or 'unread' + * @return {Promise} + */ async setNotification({ id, status }) { if (!notificationStatuses.includes(status)) { throw new Error(`invalid notification status: ${status}`) From 9366b778e6a351b93eac965f07131a202737d0c0 Mon Sep 17 00:00:00 2001 From: Franck Chastagnol Date: Fri, 30 Nov 2018 10:17:04 -0800 Subject: [PATCH 2/8] Hardening --- .../marketplace/v00_adapter.js | 65 ++++++++++++------- origin-js/src/models/notification.js | 2 +- origin-js/src/resources/marketplace.js | 54 ++++++++------- 3 files changed, 71 insertions(+), 50 deletions(-) diff --git a/origin-js/src/contractInterface/marketplace/v00_adapter.js b/origin-js/src/contractInterface/marketplace/v00_adapter.js index d1ef10a8296a..fa757dd59bb6 100644 --- a/origin-js/src/contractInterface/marketplace/v00_adapter.js +++ b/origin-js/src/contractInterface/marketplace/v00_adapter.js @@ -524,7 +524,10 @@ class V00_MarkeplaceAdapter { /** * Fetches all notifications for a user since inception. * @param {string} party - User's ETH address. - * @return {Promise} + * @return {Promise} */ async getNotifications(party) { await this.getContract() @@ -557,39 +560,53 @@ class V00_MarkeplaceAdapter { // Find events of interest on offers for listings created by the user as a seller. for (const listingId of partyListingIds) { - const listing = await this.getListing(listingId) - for (const offerId in listing.offers) { + try { + const listing = await this.getListing(listingId) + for (const offerId in listing.offers) { + const offer = listing.offers[offerId] + // Skip the event if the action was initiated by the user. + if (party === offer.event.decoded.party) { + continue + } + const type = offerStatusToSellerNotificationType[offer.status] + if (type) { + notifications.push({ + type, + event: offer.event, + resources: { listingId, offerId } + }) + } + } + } catch (e) { + // Guard against invalid listing/offer that might be created for example + // by exploiting a validation loophole in origin-js listing/offer code + // or by writing directly to the blockchain. + console.log(`Notification: skipping invalid listing ${this.contractName} ${listingId}`) + } + } + + // Find events of interest on offers made by the user as a buyer. + for (const [listingId, offerId] of partyOfferIds) { + try { + const listing = await this.getListing(listingId) const offer = listing.offers[offerId] // Skip the event if the action was initiated by the user. - if (party === offer.event.decoded.party) { + if (party.toLowerCase() === offer.event.decoded.party.toLowerCase()) { continue } - const type = offerStatusToSellerNotificationType[offer.status] + const type = offerStatusToBuyerNotificationType[offer.status] if (type) { notifications.push({ type, event: offer.event, - resources: { listingId, offerId } + resources: {listingId, offerId} }) } - } - } - - // Find events of interest on offers made by the user as a buyer. - for (const [listingId, offerId] of partyOfferIds) { - const listing = await this.getListing(listingId) - const offer = listing.offers[offerId] - // Skip the event if the action was initiated by the user. - if (party.toLowerCase() === offer.event.decoded.party.toLowerCase()) { - continue - } - const type = offerStatusToBuyerNotificationType[offer.status] - if (type) { - notifications.push({ - type, - event: offer.event, - resources: { listingId, offerId } - }) + } catch (e) { + // Guard against invalid listing/offer that might be created for example + // by exploiting a validation loophole in origin-js listing/offer code + // or by writing directly to the blockchain. + console.log(`Notification: skipping invalid offer ${this.contractName} ${offerId}`) } } diff --git a/origin-js/src/models/notification.js b/origin-js/src/models/notification.js index 940bf97d134b..ee2a3ae4edb8 100644 --- a/origin-js/src/models/notification.js +++ b/origin-js/src/models/notification.js @@ -19,7 +19,7 @@ export class Notification { this.status = status // Resources includes the following fields: // - listing: Listing model object - // - purchase: Offer model object + // - offer: Offer model object this.resources = resources } } diff --git a/origin-js/src/resources/marketplace.js b/origin-js/src/resources/marketplace.js index 3af864831d53..fc9bd64b6e9f 100644 --- a/origin-js/src/resources/marketplace.js +++ b/origin-js/src/resources/marketplace.js @@ -622,9 +622,16 @@ export default class Marketplace { /** * Fetch all notifications for the current user. * - * Note: The current implementation won't scale well, especially for sellers with large + * Notes: + * a) Only the latest notification for a given offer is returned (vs the whole history). + * Imagine the following scenario: + * - Buyer creates offer. + * - getNotification called for seller -> offer created notification returned + * - Seller accepts offer then buyer finalizes it + * - getNotification called for seller -> only the finalized notification is returned. + * b) The current implementation is very inefficient, especially for sellers with large * number of listings/offers. When this becomes an issue, the logic could be optimized. - * For example an possibility would be to add a "fromBlockNumber" argument to allow to fetch + * For example a possibility would be to add a "fromBlockNumber" argument to allow to fetch * incrementally new notifications. Alternatively, support for a "performance mode" that * fetches data from the back-end could be added. * @@ -637,20 +644,18 @@ export default class Marketplace { // Decorate each notification with listing and offer data. const withResources = await Promise.all(notifications.map(async (notification) => { - if (notification.resources.listingId) { - notification.resources.listing = await this.getListing( - generateListingId({ - version: notification.version, - network: notification.network, - listingIndex: notification.resources.listingId - }) - ) - } - let isValid = true - if (notification.resources.offerId) { - let offer - try { - offer = await this.getOffer( + try { + if (notification.resources.listingId) { + notification.resources.listing = await this.getListing( + generateListingId({ + version: notification.version, + network: notification.network, + listingIndex: notification.resources.listingId + }) + ) + } + if (notification.resources.offerId) { + notification.resources.offer = await this.getOffer( generateOfferId({ version: notification.version, network: notification.network, @@ -658,21 +663,20 @@ export default class Marketplace { offerIndex: notification.resources.offerId }) ) - } catch(e) { - // TBD: what exactly are we guarding against here ? - // Under which condition would fetching an offer raise an exception ? - isValid = false } - // FIXME: this should be renamed from purchase to offer. - notification.resources.purchase = offer - } - return isValid ? new Notification(notification) : null + return new Notification(notification) + } catch(e) { + // Guard against invalid listing/offer that might be created for example + // by exploiting a validation loophole in origin-js listing/offer code + // or by writing directly to the blockchain. + return null + } })) return withResources.filter(notification => notification !== null) } /** - * Update the status for a notification in the local store. + * Update the status of a notification in the local store. * @param {string} id - Unique notification ID * @param {string} status - 'read' or 'unread' * @return {Promise} From dbb7c64ed68fb765e66b1a62a05f432039ad7e13 Mon Sep 17 00:00:00 2001 From: Franck Chastagnol Date: Sat, 1 Dec 2018 21:08:48 -0800 Subject: [PATCH 3/8] Added unit tests --- .../src/components/notification-message.js | 5 + origin-js/scripts/test-js.js | 3 +- .../marketplace/v00_adapter.js | 24 ++- origin-js/test/helpers/as-account.js | 1 - .../test/helpers/schema-validation-helper.js | 1 + origin-js/test/resource_marketplace.test.js | 158 ++++++++++++------ 6 files changed, 134 insertions(+), 58 deletions(-) diff --git a/origin-dapp/src/components/notification-message.js b/origin-dapp/src/components/notification-message.js index 64a1d94b8153..102882e5da56 100644 --- a/origin-dapp/src/components/notification-message.js +++ b/origin-dapp/src/components/notification-message.js @@ -40,6 +40,10 @@ class NotificationMessage extends Component { id: 'notification.buyerOfferAccepted', defaultMessage: 'An offer you made has been accepted.' }, + buyerOfferDisputed: { + id: 'notification.buyerOfferDisputed', + defaultMessage: 'A problem has been reported with your transaction.' + }, buyerOfferRuling: { id: 'notification.buyerOfferRuling', defaultMessage: 'A ruling has been issued on your disputed transaction.' @@ -62,6 +66,7 @@ class NotificationMessage extends Component { 'seller_offer_ruling': intlMessages.sellerOfferRuling, 'seller_offer_withdrawn': intlMessages.sellerOfferWithdrawn, 'buyer_offer_accepted': intlMessages.buyerOfferAccepted, + 'buyer_offer_disputed': intlMessages.sellerOfferDisputed, 'buyer_offer_ruling': intlMessages.buyerOfferRuling, 'buyer_offer_review': intlMessages.buyerOfferReview, 'buyer_offer_withdrawn': intlMessages.buyerOfferWithdrawn, diff --git a/origin-js/scripts/test-js.js b/origin-js/scripts/test-js.js index dd6bf15b8f01..94d909931043 100644 --- a/origin-js/scripts/test-js.js +++ b/origin-js/scripts/test-js.js @@ -7,7 +7,8 @@ const startGanache = require('./helpers/start-ganache') const runTests = async watch => { return new Promise(() => { - const args = ['-r', '@babel/register', '-r', '@babel/polyfill', '-t', '10000'] + const args = ['-r', '@babel/register', '-r', '@babel/polyfill', '-t', '10000', '-g', 'getNotifications'] + //const args = ['-r', '@babel/register', '-r', '@babel/polyfill', '-t', '10000'] if (watch) { args.push('--watch') } else { diff --git a/origin-js/src/contractInterface/marketplace/v00_adapter.js b/origin-js/src/contractInterface/marketplace/v00_adapter.js index fa757dd59bb6..0421f723e7ef 100644 --- a/origin-js/src/contractInterface/marketplace/v00_adapter.js +++ b/origin-js/src/contractInterface/marketplace/v00_adapter.js @@ -17,6 +17,7 @@ const offerStatusToSellerNotificationType = { } const offerStatusToBuyerNotificationType = { 'accepted': 'buyer_offer_accepted', + 'disputed': 'buyer_offer_disputed', 'ruling': 'buyer_offer_ruling', 'sellerReviewed': 'buyer_offer_review', 'withdrawn': 'buyer_offer_withdrawn', @@ -557,6 +558,11 @@ class V00_MarkeplaceAdapter { ]) } } + console.log(`getNotification party=${party}`) + console.log("partyListingIds=", partyListingIds) + console.log("partyOfferIds=", partyOfferIds) + + console.log("Looking for seller side notifications...") // Find events of interest on offers for listings created by the user as a seller. for (const listingId of partyListingIds) { @@ -564,8 +570,10 @@ class V00_MarkeplaceAdapter { const listing = await this.getListing(listingId) for (const offerId in listing.offers) { const offer = listing.offers[offerId] + console.log(`offerId=${offerId} offer.event=${offer.event.event} offer.event.party=${offer.event.returnValues.party}`) // Skip the event if the action was initiated by the user. - if (party === offer.event.decoded.party) { + if (party === offer.event.returnValues.party) { + console.log("User as seller - Skipping offer ", offerId) continue } const type = offerStatusToSellerNotificationType[offer.status] @@ -581,17 +589,22 @@ class V00_MarkeplaceAdapter { // Guard against invalid listing/offer that might be created for example // by exploiting a validation loophole in origin-js listing/offer code // or by writing directly to the blockchain. - console.log(`Notification: skipping invalid listing ${this.contractName} ${listingId}`) + console.log('getNotifications: skipping invalid listing') + console.log(` contract=${this.contractName} listingId=${listingId} error=${e}`) } } + console.log("Looking for buyer side notifications...") + // Find events of interest on offers made by the user as a buyer. for (const [listingId, offerId] of partyOfferIds) { try { const listing = await this.getListing(listingId) const offer = listing.offers[offerId] + console.log(`offerId=${offerId} offer.event=${offer.event.event} offer.event.party=${offer.event.returnValues.party}`) // Skip the event if the action was initiated by the user. - if (party.toLowerCase() === offer.event.decoded.party.toLowerCase()) { + if (party.toLowerCase() === offer.event.returnValues.party.toLowerCase()) { + console.log("User as buyer - Skipping offer ", offerId) continue } const type = offerStatusToBuyerNotificationType[offer.status] @@ -606,10 +619,11 @@ class V00_MarkeplaceAdapter { // Guard against invalid listing/offer that might be created for example // by exploiting a validation loophole in origin-js listing/offer code // or by writing directly to the blockchain. - console.log(`Notification: skipping invalid offer ${this.contractName} ${offerId}`) + console.log('getNotifications: skipping invalid offer') + console.log(` contract=${this.contractName} offerId=${listingId} error=${e}`) } } - + console.log("NUM NOTIFICATIONS RETURNED=", notifications.length) return notifications } diff --git a/origin-js/test/helpers/as-account.js b/origin-js/test/helpers/as-account.js index 0ded69fc516b..6ff25cf523ea 100644 --- a/origin-js/test/helpers/as-account.js +++ b/origin-js/test/helpers/as-account.js @@ -1,5 +1,4 @@ export default async function asAccount(web3, account, fn) { - const accounts = await web3.eth.getAccounts() const accountBefore = web3.eth.defaultAccount web3.eth.defaultAccount = account const result = await fn() diff --git a/origin-js/test/helpers/schema-validation-helper.js b/origin-js/test/helpers/schema-validation-helper.js index c0de800d3d97..8432fa7a5c57 100644 --- a/origin-js/test/helpers/schema-validation-helper.js +++ b/origin-js/test/helpers/schema-validation-helper.js @@ -130,6 +130,7 @@ export const validateNotification = (notification) => { expect(notification.resources).to.have.property('listingId').that.is.a('string') expect(notification.resources).to.have.property('offerId').that.is.a('string') expect(notification.resources).to.have.property('listing').that.is.an('object') + expect(notification.resources).to.have.property('offer').that.is.an('object') } export const validateMessaging = (messaging) => { diff --git a/origin-js/test/resource_marketplace.test.js b/origin-js/test/resource_marketplace.test.js index 05626d6424da..6cc4c6be0cce 100644 --- a/origin-js/test/resource_marketplace.test.js +++ b/origin-js/test/resource_marketplace.test.js @@ -21,6 +21,7 @@ const multiUnitListingData = Object.assign({}, listingValid, { unitsTotal: 2 }) const multiUnitListingWithCommissionData = Object.assign( {}, multiUnitListingData, + { commission: { currency: 'OGN', amount: '2' }, commissionPerUnit: { currency: 'OGN', amount: '1' } @@ -106,8 +107,18 @@ describe('Marketplace Resource', function() { store }) + // Set default account for contract calls. + // Use helper method asAccount to make calls on behalf of a different user. + contractService.web3.eth.defaultAccount = accounts[0] + + // Create a listing using default account. await marketplace.createListing(listingData) - await marketplace.makeOffer('999-000-0', offerData) + + // Make an offer on that listing using the buyer account. + await asAccount(contractService.web3, validBuyer, async () => { + await marketplace.makeOffer('999-000-0', offerData) + }) + makeMaliciousOffer = async ({ affiliate = validAffiliate, arbitrator = validArbitrator }) => { const ipfsHash = await marketplace.ipfsDataStore.save(OFFER_DATA_TYPE, offerData) const ipfsBytes = contractService.getBytes32FromIpfsHash(ipfsHash) @@ -157,13 +168,7 @@ describe('Marketplace Resource', function() { }) it('should return listing data as it was when an offer was made with purchasesFor option', async () => { - await asAccount(contractService.web3, validBuyer, async () => { - await marketplace.makeOffer('999-000-0', offerData) - }) - - await asAccount(contractService.web3, this.userAddress, async () => { - await marketplace.updateListing('999-000-0', udpatedListingData) - }) + await marketplace.updateListing('999-000-0', udpatedListingData) const listings = await marketplace.getListings({ purchasesFor: validBuyer, @@ -424,7 +429,9 @@ describe('Marketplace Resource', function() { let offer = await marketplace.getOffer('999-000-0-0') expect(offer.status).to.equal('created') await marketplace.acceptOffer('999-000-0-0') - await marketplace.finalizeOffer('999-000-0-0', reviewData) + await asAccount(contractService.web3, validBuyer, async () => { + await marketplace.finalizeOffer('999-000-0-0', reviewData) + }) offer = await marketplace.getOffer('999-000-0-0') validateOffer(offer) @@ -434,39 +441,25 @@ describe('Marketplace Resource', function() { describe('myPurchases', () => { it('should return a user\'s purchases with listing data as it was at the time of the offer', async () => { - await asAccount(contractService.web3, validBuyer, async () => { - await marketplace.makeOffer('999-000-0', offerData) - }) - - await asAccount(contractService.web3, this.userAddress, async () => { - await marketplace.updateListing('999-000-0', udpatedListingData) - }) - + await marketplace.updateListing('999-000-0', udpatedListingData) const purchases = await marketplace.getPurchases(validBuyer) expect(purchases).to.be.an('array') - expect(purchases.length).to.equal(2) - expect(purchases[1].offer.listingId).to.equal('999-000-0') - expect(purchases[1].listing.title).to.equal('my listing') // not 'my listing EDITED!' + expect(purchases.length).to.equal(1) + expect(purchases[0].offer.listingId).to.equal('999-000-0') + expect(purchases[0].listing.title).to.equal('my listing') // not 'my listing EDITED!' }) }) describe('mySales', () => { it('should return a seller\'s sales with listing data as it was at the time of the offer', async () => { - await asAccount(contractService.web3, validBuyer, async () => { - await marketplace.makeOffer('999-000-0', offerData) - }) - - await asAccount(contractService.web3, this.userAddress, async () => { - await marketplace.updateListing('999-000-0', udpatedListingData) - }) - + await marketplace.updateListing('999-000-0', udpatedListingData) const sales = await marketplace.getSales(this.userAddress) expect(sales).to.be.an('array') - expect(sales.length).to.equal(2) - expect(sales[1].offer.listingId).to.equal('999-000-0') - expect(sales[1].listing.title).to.equal('my listing') // not 'my listing EDITED!' + expect(sales.length).to.equal(1) + expect(sales[0].offer.listingId).to.equal('999-000-0') + expect(sales[0].listing.title).to.equal('my listing') // not 'my listing EDITED!' }) }) @@ -475,7 +468,9 @@ describe('Marketplace Resource', function() { let offer = await marketplace.getOffer('999-000-0-0') expect(offer.status).to.equal('created') await marketplace.acceptOffer('999-000-0-0') - await marketplace.finalizeOffer('999-000-0-0', reviewData) + await asAccount(contractService.web3, validBuyer, async () => { + await marketplace.finalizeOffer('999-000-0-0', reviewData) + }) await marketplace.addData(0, offer.id, reviewData) offer = await marketplace.getOffer('999-000-0-0') @@ -487,7 +482,9 @@ describe('Marketplace Resource', function() { describe('getListingReviews', () => { it('should get reviews', async () => { await marketplace.acceptOffer('999-000-0-0') - await marketplace.finalizeOffer('999-000-0-0', reviewData) + await asAccount(contractService.web3, validBuyer, async () => { + await marketplace.finalizeOffer('999-000-0-0', reviewData) + }) const reviews = await marketplace.getListingReviews('999-000-0') expect(reviews.length).to.equal(1) expect(reviews[0].rating).to.equal(3) @@ -498,41 +495,100 @@ describe('Marketplace Resource', function() { describe('getNotifications', () => { let notifications - beforeEach(async function() { - notifications = await marketplace.getNotifications() + function expectNotification(type, eventName) { expect(notifications.length).to.equal(1) validateNotification(notifications[0]) - expect(notifications[0].type).to.equal('seller_listing_purchased') + expect(notifications[0].type).to.equal(type) expect(notifications[0].status).to.equal('unread') + expect(notifications[0].event.event).to.equal(eventName) + } + + beforeEach(async function() { + // Before each test a listing is created with an offer from a buyer. + // Therefore seller should receive a notification for it. + notifications = await marketplace.getNotifications() + expectNotification('seller_offer_created', 'OfferCreated') }) it('should return notifications', async () => { + // Seller accepts the offer. Buyer should receive a notification. await marketplace.acceptOffer('999-000-0-0') + await asAccount(contractService.web3, validBuyer, async () => { + notifications = await marketplace.getNotifications() + }) + expectNotification('buyer_offer_accepted', 'OfferAccepted') + + // Buyer finalizes, seller should receive a notification. + await asAccount(contractService.web3, validBuyer, async () => { + await marketplace.finalizeOffer('999-000-0-0', reviewData) + }) notifications = await marketplace.getNotifications() - expect(notifications.length).to.equal(1) - validateNotification(notifications[0]) + expectNotification('seller_offer_finalized', 'OfferFinalized') - expect(notifications[0].type).to.equal('buyer_listing_shipped') - expect(notifications[0].status).to.equal('unread') - expect(notifications[0].event.event).to.equal('OfferAccepted') + // Seller writes a review, buyer should receive a notification. + await marketplace.addData(0, '999-000-0-0', reviewData) + await asAccount(contractService.web3, validBuyer, async () => { + notifications = await marketplace.getNotifications() + }) + expectNotification('buyer_offer_review', 'OfferData') + }) + + it('buyer should get a notifications when offer rejected by seller', async () => { + // Seller rejects offer, buyer should receive a notification. + await marketplace.withdrawOffer('999-000-0-0') + await asAccount(contractService.web3, validBuyer, async () => { + notifications = await marketplace.getNotifications() + }) + expectNotification('buyer_offer_withdrawn', 'OfferWithdrawn') + }) - await marketplace.finalizeOffer('999-000-0-0', reviewData) + it('seller should get a notifications when offer withdrawn by buyer', async () => { + // Seller rejects offer, buyer should receive a notification. + await asAccount(contractService.web3, validBuyer, async () => { + await marketplace.withdrawOffer('999-000-0-0') + }) notifications = await marketplace.getNotifications() - expect(notifications.length).to.equal(1) - validateNotification(notifications[0]) + expectNotification('seller_offer_withdrawn', 'OfferWithdrawn') + }) - expect(notifications[0].type).to.equal('seller_review_received') - expect(notifications[0].status).to.equal('unread') - expect(notifications[0].event.event).to.equal('OfferFinalized') + it('Should get a notifications when offer disputed and ruled', async () => { + await marketplace.acceptOffer('999-000-0-0') + // Buyer initiates dispute, seller should get a notification. + await asAccount(contractService.web3, validBuyer, async () => { + await marketplace.initiateDispute('999-000-0-0') + }) + notifications = await marketplace.getNotifications() + expectNotification('seller_offer_disputed', 'OfferDisputed') + + // Dispute ruled, both buyer and seller should get a notification. + await asAccount(contractService.web3, validArbitrator, async () => { + await marketplace.resolveDispute('999-000-0-0', {}, 1, 0) + }) + notifications = await marketplace.getNotifications() + expectNotification('seller_offer_ruling', 'OfferRuling') + + await asAccount(contractService.web3, validBuyer, async () => { + notifications = await marketplace.getNotifications() + }) + expectNotification('buyer_offer_ruling', 'OfferRuling') }) - it('should exclude notifications for invalid offers', async () => { - await marketplace.makeOffer('999-000-0', invalidPriceOffer) + it('Buyer should get a notifications when offer disputed by seller', async () => { + await marketplace.acceptOffer('999-000-0-0') + await marketplace.initiateDispute('999-000-0-0') + await asAccount(contractService.web3, validBuyer, async () => { + notifications = await marketplace.getNotifications() + }) + expectNotification('buyer_offer_disputed', 'OfferDisputed') + }) - const notifications = await marketplace.getNotifications() + it('should exclude notifications for invalid offers', async () => { + await asAccount(contractService.web3, validBuyer, async () => { + await marketplace.makeOffer('999-000-0', invalidPriceOffer) + }) + notifications = await marketplace.getNotifications() expect(notifications.length).to.equal(1) - validateNotification(notifications[0]) expect(notifications).to.not.include(invalidPriceOffer) }) }) From ed2bd7e6306c67d69b421759201bf811251de428 Mon Sep 17 00:00:00 2001 From: Franck Chastagnol Date: Sat, 1 Dec 2018 21:13:44 -0800 Subject: [PATCH 4/8] Translation messages --- origin-dapp/translations/all-messages.json | 14 +++++-- .../src/components/notification-message.json | 38 +++++++++++++++---- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/origin-dapp/translations/all-messages.json b/origin-dapp/translations/all-messages.json index 897e439ccf90..13813573b2aa 100644 --- a/origin-dapp/translations/all-messages.json +++ b/origin-dapp/translations/all-messages.json @@ -287,10 +287,16 @@ "navbar.addListing": "Add a Listing", "not-found.heading": "How did I get here?", "not-found.content": "The page you’re looking for is no longer here, maybe it was never here in the first place. In any case, we sincerely apologize if it’s us and we forgive you if it’s you :)", - "notification.offerMade": "You have a new offer.", - "notification.purchaseSent": "Your offer has been accepted.", - "notification.saleConfirmed": "Your sale has been confirmed.", - "notification.sellerReviewed": "You have a new review.", + "notification.sellerOfferCreated": "You have a new offer.", + "notification.sellerOfferFinalized": "Your transaction has been completed.", + "notification.sellerOfferDisputed": "A problem has been reported with your transaction.", + "notification.sellerOfferWithdrawn": "An offer on your listing has been withdrawn.", + "notification.sellerOfferRuling": "A ruling has been issued on your disputed transaction.", + "notification.buyerOfferAccepted": "An offer you made has been accepted.", + "notification.buyerOfferDisputed": "A problem has been reported with your transaction.", + "notification.buyerOfferRuling": "A ruling has been issued on your disputed transaction.", + "notification.buyerOfferReview": "A review has been left on your transaction.", + "notification.buyerOfferWithdrawn": "An offer you made has been rejected.", "notification.buyer": "Buyer", "notification.seller": "Seller", "notificationsComponent.notificationsHeading": "Notifications", diff --git a/origin-dapp/translations/messages/src/components/notification-message.json b/origin-dapp/translations/messages/src/components/notification-message.json index eaa7bdc89511..4459f7862af1 100644 --- a/origin-dapp/translations/messages/src/components/notification-message.json +++ b/origin-dapp/translations/messages/src/components/notification-message.json @@ -1,18 +1,42 @@ [ { - "id": "notification.offerMade", + "id": "notification.sellerOfferCreated", "defaultMessage": "You have a new offer." }, { - "id": "notification.purchaseSent", - "defaultMessage": "Your offer has been accepted." + "id": "notification.sellerOfferFinalized", + "defaultMessage": "Your transaction has been completed." }, { - "id": "notification.saleConfirmed", - "defaultMessage": "Your sale has been confirmed." + "id": "notification.sellerOfferDisputed", + "defaultMessage": "A problem has been reported with your transaction." }, { - "id": "notification.sellerReviewed", - "defaultMessage": "You have a new review." + "id": "notification.sellerOfferWithdrawn", + "defaultMessage": "An offer on your listing has been withdrawn." + }, + { + "id": "notification.sellerOfferRuling", + "defaultMessage": "A ruling has been issued on your disputed transaction." + }, + { + "id": "notification.buyerOfferAccepted", + "defaultMessage": "An offer you made has been accepted." + }, + { + "id": "notification.buyerOfferDisputed", + "defaultMessage": "A problem has been reported with your transaction." + }, + { + "id": "notification.buyerOfferRuling", + "defaultMessage": "A ruling has been issued on your disputed transaction." + }, + { + "id": "notification.buyerOfferReview", + "defaultMessage": "A review has been left on your transaction." + }, + { + "id": "notification.buyerOfferWithdrawn", + "defaultMessage": "An offer you made has been rejected." } ] \ No newline at end of file From 1c1620f787ff05af776d720e25df95b3c1565f7e Mon Sep 17 00:00:00 2001 From: Franck Chastagnol Date: Sat, 1 Dec 2018 21:24:26 -0800 Subject: [PATCH 5/8] Update doc and delete debug statements --- .../source/includes/resources/marketplace.md | 17 +++++++---------- .../marketplace/v00_adapter.js | 12 ------------ 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/origin-docs/source/includes/resources/marketplace.md b/origin-docs/source/includes/resources/marketplace.md index bbc78a621424..75a60b85fc9a 100644 --- a/origin-docs/source/includes/resources/marketplace.md +++ b/origin-docs/source/includes/resources/marketplace.md @@ -154,21 +154,18 @@ const listingId = "927-832" ## getNotifications -Each Notification corresponds to the status of a Listing. Notifications are currently generated for each of the following Listing statuses: +Each Notification corresponds to the status of an Offer. Notifications are currently generated for each of the following Offer statuses: -- ListingCreated -- ListingUpdated -- ListingWithdrawn -- ListingArbitrated - OfferCreated - OfferAccepted - OfferFinalized - OfferWithdrawn -- OfferFundsAdded - OfferDisputed - OfferRuling +- OfferFinalized +- OfferData -Notifications do not exist on the blockchain nor are they read from a database. They are derived from the blockchain transaction logs of the Listing statuses at the time of the API request. Because of this, there is no central record of a notification's status as "read" or "unread". When a client first interacts with the notifications API, Origin.js will record a timestamp in local storage. All notifications resulting from blockchain events that happen prior to this timestamp will be considered to be "read". This ensures that when the same user interacts with the notifications API from a different client for the first time, they will not receive a large number of "unread" notifications that they have previously read from their original client. +Notifications do not exist on the blockchain nor are they read from a database. They are derived from the blockchain transaction logs of the Offer statuses at the time of the API request. Because of this, there is no central record of a notification's status as "read" or "unread". When a client first interacts with the notifications API, Origin.js will record a timestamp in local storage. All notifications resulting from blockchain events that happen prior to this timestamp will be considered to be "read". This ensures that when the same user interacts with the notifications API from a different client for the first time, they will not receive a large number of "unread" notifications that they have previously read from their original client. > Example: getNotifications @@ -180,10 +177,10 @@ Notifications do not exist on the blockchain nor are they read from a database. [{ "id": "2984803-23433", - "type": "buyer_listing_shipped", + "type": "buyer_offer_accepted", "status": "unread", - "event": {}, - "resources": { listingId: "927-832", offerId: "183", listing: { title: "Whirlpool Microwave" } } + "event": {...}, + "resources": { listingId: "1-000-832", offerId: "183", listing: { title: "Whirlpool Microwave" }, offer: {...} } ]} ``` diff --git a/origin-js/src/contractInterface/marketplace/v00_adapter.js b/origin-js/src/contractInterface/marketplace/v00_adapter.js index 0421f723e7ef..b4d2eb7b3a25 100644 --- a/origin-js/src/contractInterface/marketplace/v00_adapter.js +++ b/origin-js/src/contractInterface/marketplace/v00_adapter.js @@ -558,11 +558,6 @@ class V00_MarkeplaceAdapter { ]) } } - console.log(`getNotification party=${party}`) - console.log("partyListingIds=", partyListingIds) - console.log("partyOfferIds=", partyOfferIds) - - console.log("Looking for seller side notifications...") // Find events of interest on offers for listings created by the user as a seller. for (const listingId of partyListingIds) { @@ -570,10 +565,8 @@ class V00_MarkeplaceAdapter { const listing = await this.getListing(listingId) for (const offerId in listing.offers) { const offer = listing.offers[offerId] - console.log(`offerId=${offerId} offer.event=${offer.event.event} offer.event.party=${offer.event.returnValues.party}`) // Skip the event if the action was initiated by the user. if (party === offer.event.returnValues.party) { - console.log("User as seller - Skipping offer ", offerId) continue } const type = offerStatusToSellerNotificationType[offer.status] @@ -594,17 +587,13 @@ class V00_MarkeplaceAdapter { } } - console.log("Looking for buyer side notifications...") - // Find events of interest on offers made by the user as a buyer. for (const [listingId, offerId] of partyOfferIds) { try { const listing = await this.getListing(listingId) const offer = listing.offers[offerId] - console.log(`offerId=${offerId} offer.event=${offer.event.event} offer.event.party=${offer.event.returnValues.party}`) // Skip the event if the action was initiated by the user. if (party.toLowerCase() === offer.event.returnValues.party.toLowerCase()) { - console.log("User as buyer - Skipping offer ", offerId) continue } const type = offerStatusToBuyerNotificationType[offer.status] @@ -623,7 +612,6 @@ class V00_MarkeplaceAdapter { console.log(` contract=${this.contractName} offerId=${listingId} error=${e}`) } } - console.log("NUM NOTIFICATIONS RETURNED=", notifications.length) return notifications } From 5f4b0e1f3c3cf0b79a0807c63c4ae8a80476b750 Mon Sep 17 00:00:00 2001 From: Franck Chastagnol Date: Sat, 1 Dec 2018 21:27:18 -0800 Subject: [PATCH 6/8] lint fixes --- origin-js/src/contractInterface/marketplace/v00_adapter.js | 2 +- origin-js/test/resource_marketplace.test.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/origin-js/src/contractInterface/marketplace/v00_adapter.js b/origin-js/src/contractInterface/marketplace/v00_adapter.js index b4d2eb7b3a25..b0b1d1502c2c 100644 --- a/origin-js/src/contractInterface/marketplace/v00_adapter.js +++ b/origin-js/src/contractInterface/marketplace/v00_adapter.js @@ -601,7 +601,7 @@ class V00_MarkeplaceAdapter { notifications.push({ type, event: offer.event, - resources: {listingId, offerId} + resources: { listingId, offerId } }) } } catch (e) { diff --git a/origin-js/test/resource_marketplace.test.js b/origin-js/test/resource_marketplace.test.js index 6cc4c6be0cce..13b1cd7e676f 100644 --- a/origin-js/test/resource_marketplace.test.js +++ b/origin-js/test/resource_marketplace.test.js @@ -756,7 +756,7 @@ describe('Marketplace Resource', function() { // Create a second offer for 1 unit. await marketplace.makeOffer('999-000-1', offerData) - let offer2 = await marketplace.getOffer('999-000-1-2') + const offer2 = await marketplace.getOffer('999-000-1-2') expect(offer2.status).to.equal('created') validateOffer(offer2) @@ -839,7 +839,7 @@ describe('Marketplace Resource', function() { // Create and withdraw an offer for 1 unit. await marketplace.makeOffer('999-000-1', offerData) - let offer2 = await marketplace.getOffer('999-000-1-1') + const offer2 = await marketplace.getOffer('999-000-1-1') expect(offer2.status).to.equal('created') await marketplace.withdrawOffer('999-000-1-1') @@ -860,7 +860,7 @@ describe('Marketplace Resource', function() { const newOfferData = Object.assign( {}, offerData, - { unitsPurchased: newUnitsTotal} + { unitsPurchased: newUnitsTotal } ) // Make an offer for too many units, which should fail. From 731ece9b1d8d7bc07db50bbe23bbdfcfc3484173 Mon Sep 17 00:00:00 2001 From: Franck Chastagnol Date: Sun, 2 Dec 2018 21:00:35 -0800 Subject: [PATCH 7/8] Tweaks --- origin-js/scripts/test-js.js | 3 +-- origin-js/src/contractInterface/marketplace/v00_adapter.js | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/origin-js/scripts/test-js.js b/origin-js/scripts/test-js.js index 94d909931043..dd6bf15b8f01 100644 --- a/origin-js/scripts/test-js.js +++ b/origin-js/scripts/test-js.js @@ -7,8 +7,7 @@ const startGanache = require('./helpers/start-ganache') const runTests = async watch => { return new Promise(() => { - const args = ['-r', '@babel/register', '-r', '@babel/polyfill', '-t', '10000', '-g', 'getNotifications'] - //const args = ['-r', '@babel/register', '-r', '@babel/polyfill', '-t', '10000'] + const args = ['-r', '@babel/register', '-r', '@babel/polyfill', '-t', '10000'] if (watch) { args.push('--watch') } else { diff --git a/origin-js/src/contractInterface/marketplace/v00_adapter.js b/origin-js/src/contractInterface/marketplace/v00_adapter.js index b0b1d1502c2c..c3c3ed9c77c1 100644 --- a/origin-js/src/contractInterface/marketplace/v00_adapter.js +++ b/origin-js/src/contractInterface/marketplace/v00_adapter.js @@ -566,7 +566,7 @@ class V00_MarkeplaceAdapter { for (const offerId in listing.offers) { const offer = listing.offers[offerId] // Skip the event if the action was initiated by the user. - if (party === offer.event.returnValues.party) { + if (party.toLowerCase() === offer.event.returnValues.party.toLowerCase()) { continue } const type = offerStatusToSellerNotificationType[offer.status] @@ -609,7 +609,7 @@ class V00_MarkeplaceAdapter { // by exploiting a validation loophole in origin-js listing/offer code // or by writing directly to the blockchain. console.log('getNotifications: skipping invalid offer') - console.log(` contract=${this.contractName} offerId=${listingId} error=${e}`) + console.log(` contract=${this.contractName} offerId=${offerId} error=${e}`) } } return notifications From b47d4659b66bce140786fb718b7abed1ae76356b Mon Sep 17 00:00:00 2001 From: Franck Chastagnol Date: Mon, 3 Dec 2018 11:44:00 -0800 Subject: [PATCH 8/8] Tested and fixed couple glitches in DApp --- origin-dapp/src/components/notification-message.js | 2 +- origin-dapp/src/components/notification.js | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/origin-dapp/src/components/notification-message.js b/origin-dapp/src/components/notification-message.js index 102882e5da56..59af3c2f2212 100644 --- a/origin-dapp/src/components/notification-message.js +++ b/origin-dapp/src/components/notification-message.js @@ -75,7 +75,7 @@ class NotificationMessage extends Component { render() { const { className, type } = this.props - let message = this.notificationTypeToMessage(type) + let message = this.notificationTypeToMessage[type] if (!message) { return

{NON_PURCHASE_RELATED_MESSAGE}

} diff --git a/origin-dapp/src/components/notification.js b/origin-dapp/src/components/notification.js index 819780879392..210fb92aa37b 100644 --- a/origin-dapp/src/components/notification.js +++ b/origin-dapp/src/components/notification.js @@ -17,8 +17,8 @@ class Notification extends Component { super(props) const { notification, wallet } = this.props - const { listing, purchase } = notification.resources - const counterpartyAddress = [listing.seller, purchase.buyer].find( + const { listing, offer } = notification.resources + const counterpartyAddress = [listing.seller, offer.buyer].find( addr => formattedAddress(addr) !== formattedAddress(wallet.address) ) @@ -27,7 +27,7 @@ class Notification extends Component { counterpartyAddress, counterpartyName: '', listing, - purchase + offer } } @@ -58,7 +58,7 @@ class Notification extends Component { counterpartyAddress, counterpartyName, listing, - purchase + offer } = this.state const listingImageURL = @@ -66,7 +66,7 @@ class Notification extends Component { return (
  • - +
    {!listing.id && (