From 40219cdaab86769fe325deb8ac9548657c328d12 Mon Sep 17 00:00:00 2001 From: davidejensen Date: Fri, 14 Aug 2026 18:07:19 +0200 Subject: [PATCH 1/8] feat: added PBExplorerPurchaseEventResult and iap open event --- .../kernel/apis/restricted_actions.proto | 7 +++++++ .../sdk/components/common/explorer_ui.proto | 1 + .../explorer_purchase_event_result.proto | 21 +++++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 proto/decentraland/sdk/components/explorer_purchase_event_result.proto diff --git a/proto/decentraland/kernel/apis/restricted_actions.proto b/proto/decentraland/kernel/apis/restricted_actions.proto index 2a8c839f..3df3aed1 100644 --- a/proto/decentraland/kernel/apis/restricted_actions.proto +++ b/proto/decentraland/kernel/apis/restricted_actions.proto @@ -83,6 +83,13 @@ message OpenExplorerUiRequest { // own optional param message without touching existing fields. // message MapParams { int32 focus_x = 1; int32 focus_y = 2; } // oneof params { MapParams map = 10; } + message PurchaseParams { + string urn = 1; + } + + oneof params { + PurchaseParams purchase = 10; + } } message OpenExplorerUiResponse { diff --git a/proto/decentraland/sdk/components/common/explorer_ui.proto b/proto/decentraland/sdk/components/common/explorer_ui.proto index 6abf3598..0bd72456 100644 --- a/proto/decentraland/sdk/components/common/explorer_ui.proto +++ b/proto/decentraland/sdk/components/common/explorer_ui.proto @@ -11,4 +11,5 @@ enum ExplorerUi { EU_COMMUNITIES = 4; EU_PLACES = 5; EU_EVENTS = 6; + EU_ITEM_PURCHASE = 7; } diff --git a/proto/decentraland/sdk/components/explorer_purchase_event_result.proto b/proto/decentraland/sdk/components/explorer_purchase_event_result.proto new file mode 100644 index 00000000..48352bcb --- /dev/null +++ b/proto/decentraland/sdk/components/explorer_purchase_event_result.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; +package decentraland.sdk.components; + +import "decentraland/sdk/components/common/id.proto"; + +option (common.ecs_component_id) = 1221; + +// Grow-only value set appended to the scene root entity. +// Reports the outcome of an EU_ITEM_PURCHASE flow triggered via OpenExplorerUi. +message PBExplorerPurchaseEventResult { + enum PurchaseResult { + PR_UNSPECIFIED = 0; + PR_PURCHASED = 1; // purchase confirmed, transaction broadcast + PR_DISMISSED = 2; // player closed confirmation without buying + PR_FAILED = 3; // catch-all for any failure (coarse by design) + } + + string urn = 1; + PurchaseResult result = 2; + uint32 timestamp = 3; +} \ No newline at end of file From c65b4c166bce67a7db95bd39d674e4475d2cc029 Mon Sep 17 00:00:00 2001 From: davidejensen Date: Mon, 17 Aug 2026 09:01:14 +0200 Subject: [PATCH 2/8] Refactor and improvements --- .../kernel/apis/restricted_actions.proto | 6 +---- .../sdk/components/common/explorer_ui.proto | 6 ++++- .../explorer_item_purchase_event_result.proto | 23 +++++++++++++++++++ .../explorer_purchase_event_result.proto | 21 ----------------- .../explorer_ui_events_result.proto | 6 ++--- public/sdk-components.proto | 1 + 6 files changed, 33 insertions(+), 30 deletions(-) create mode 100644 proto/decentraland/sdk/components/explorer_item_purchase_event_result.proto delete mode 100644 proto/decentraland/sdk/components/explorer_purchase_event_result.proto diff --git a/proto/decentraland/kernel/apis/restricted_actions.proto b/proto/decentraland/kernel/apis/restricted_actions.proto index 3df3aed1..dd4249f7 100644 --- a/proto/decentraland/kernel/apis/restricted_actions.proto +++ b/proto/decentraland/kernel/apis/restricted_actions.proto @@ -70,7 +70,7 @@ message StopEmoteRequest { } enum OpenExplorerUiResult { UNSPECIFIED = 0; OPENED = 1; - WAS_ALREADY_OPEN = 2; // a fullscreen panel is already open + WAS_ALREADY_OPEN = 2; // a panel is already open REJECTED_NOT_CURRENT_SCENE = 3; // the standard restricted-actions current-scene gate rejected the call REJECTED_FEATURE_DISABLED = 4; // the requested section is hidden by feature flags or client doesn't have that feature REJECTED_NO_USER_GESTURE = 5; // rejected: the call did not originate from a user gesture @@ -79,10 +79,6 @@ enum OpenExplorerUiResult { message OpenExplorerUiRequest { decentraland.sdk.components.common.ExplorerUi ui = 1; - // Extension point for future per-panel parameters, as a oneof so each panel gets its - // own optional param message without touching existing fields. - // message MapParams { int32 focus_x = 1; int32 focus_y = 2; } - // oneof params { MapParams map = 10; } message PurchaseParams { string urn = 1; } diff --git a/proto/decentraland/sdk/components/common/explorer_ui.proto b/proto/decentraland/sdk/components/common/explorer_ui.proto index 0bd72456..e0d3072a 100644 --- a/proto/decentraland/sdk/components/common/explorer_ui.proto +++ b/proto/decentraland/sdk/components/common/explorer_ui.proto @@ -11,5 +11,9 @@ enum ExplorerUi { EU_COMMUNITIES = 4; EU_PLACES = 5; EU_EVENTS = 6; - EU_ITEM_PURCHASE = 7; + // Opens the item-purchase confirmation modal for the urn in PurchaseParams. + // UiOpened/UiClosed are emitted in PBExplorerItemPurchaseResult like for every other value; + // the purchase outcome is reported via the dedicated result component (1221). + EU_ITEM_PURCHASE = 7; + } diff --git a/proto/decentraland/sdk/components/explorer_item_purchase_event_result.proto b/proto/decentraland/sdk/components/explorer_item_purchase_event_result.proto new file mode 100644 index 00000000..fa24e0d9 --- /dev/null +++ b/proto/decentraland/sdk/components/explorer_item_purchase_event_result.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; +package decentraland.sdk.components; + +import "decentraland/sdk/components/common/id.proto"; + +option (common.ecs_component_id) = 1221; + +// Grow-only value set appended to the scene root entity. +// Reports the outcome of an EU_ITEM_PURCHASE flow triggered via OpenExplorerUi. +message PBExplorerItemPurchaseResult { + message Purchased {} + message Dismissed {} + message Failed {} + + string urn = 1; + uint32 timestamp = 2; + + oneof status { + Purchased purchased = 10; + Dismissed dismissed = 11; + Failed failed = 12; + } +} \ No newline at end of file diff --git a/proto/decentraland/sdk/components/explorer_purchase_event_result.proto b/proto/decentraland/sdk/components/explorer_purchase_event_result.proto deleted file mode 100644 index 48352bcb..00000000 --- a/proto/decentraland/sdk/components/explorer_purchase_event_result.proto +++ /dev/null @@ -1,21 +0,0 @@ -syntax = "proto3"; -package decentraland.sdk.components; - -import "decentraland/sdk/components/common/id.proto"; - -option (common.ecs_component_id) = 1221; - -// Grow-only value set appended to the scene root entity. -// Reports the outcome of an EU_ITEM_PURCHASE flow triggered via OpenExplorerUi. -message PBExplorerPurchaseEventResult { - enum PurchaseResult { - PR_UNSPECIFIED = 0; - PR_PURCHASED = 1; // purchase confirmed, transaction broadcast - PR_DISMISSED = 2; // player closed confirmation without buying - PR_FAILED = 3; // catch-all for any failure (coarse by design) - } - - string urn = 1; - PurchaseResult result = 2; - uint32 timestamp = 3; -} \ No newline at end of file diff --git a/proto/decentraland/sdk/components/explorer_ui_events_result.proto b/proto/decentraland/sdk/components/explorer_ui_events_result.proto index 69f5c0c6..5c3ea859 100644 --- a/proto/decentraland/sdk/components/explorer_ui_events_result.proto +++ b/proto/decentraland/sdk/components/explorer_ui_events_result.proto @@ -6,14 +6,14 @@ import "decentraland/sdk/components/common/explorer_ui.proto"; option (common.ecs_component_id) = 1220; -// PBExplorerUiEventsResult transports the lifecycle events of fullscreen explorer panels — a panel was +// PBExplorerUiEventsResult transports the lifecycle events of explorer UI panels — a panel was // opened, a panel was closed. It is a grow only value set appended to the scene root entity, so every // event of a tick is delivered and none overwrites another. message PBExplorerUiEventsResult { - // Emitted when a fullscreen explorer panel is opened. + // Emitted when an explorer panel is opened. message UiOpened {} - // Emitted when a fullscreen explorer panel is closed. + // Emitted when an explorer panel is closed. message UiClosed {} decentraland.sdk.components.common.ExplorerUi ui = 1; // The panel that the event refers to diff --git a/public/sdk-components.proto b/public/sdk-components.proto index f1a19d05..4f1216cd 100644 --- a/public/sdk-components.proto +++ b/public/sdk-components.proto @@ -44,3 +44,4 @@ import public "decentraland/sdk/components/ui_text.proto"; import public "decentraland/sdk/components/ui_transform.proto"; import public "decentraland/sdk/components/video_player.proto"; import public "decentraland/sdk/components/visibility_component.proto"; +import public "decentraland/sdk/components/explorer_item_purchase_event_result.proto"; From 7a07f26014bcbf9fea515559eee47d4bca3262dd Mon Sep 17 00:00:00 2001 From: davidejensen Date: Mon, 17 Aug 2026 14:28:43 +0200 Subject: [PATCH 3/8] Code suggestions --- proto/decentraland/sdk/components/common/explorer_ui.proto | 7 +++---- ...nt_result.proto => explorer_item_purchase_result.proto} | 4 ++-- public/sdk-components.proto | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) rename proto/decentraland/sdk/components/{explorer_item_purchase_event_result.proto => explorer_item_purchase_result.proto} (88%) diff --git a/proto/decentraland/sdk/components/common/explorer_ui.proto b/proto/decentraland/sdk/components/common/explorer_ui.proto index e0d3072a..473af3fa 100644 --- a/proto/decentraland/sdk/components/common/explorer_ui.proto +++ b/proto/decentraland/sdk/components/common/explorer_ui.proto @@ -12,8 +12,7 @@ enum ExplorerUi { EU_PLACES = 5; EU_EVENTS = 6; // Opens the item-purchase confirmation modal for the urn in PurchaseParams. - // UiOpened/UiClosed are emitted in PBExplorerItemPurchaseResult like for every other value; - // the purchase outcome is reported via the dedicated result component (1221). - EU_ITEM_PURCHASE = 7; - + // UiOpened/UiClosed are emitted in PBExplorerUiEventsResult like for every other value; + // the purchase outcome is reported via the dedicated result component (1220). + EU_ITEM_PURCHASE = 7; } diff --git a/proto/decentraland/sdk/components/explorer_item_purchase_event_result.proto b/proto/decentraland/sdk/components/explorer_item_purchase_result.proto similarity index 88% rename from proto/decentraland/sdk/components/explorer_item_purchase_event_result.proto rename to proto/decentraland/sdk/components/explorer_item_purchase_result.proto index fa24e0d9..46e0a8dd 100644 --- a/proto/decentraland/sdk/components/explorer_item_purchase_event_result.proto +++ b/proto/decentraland/sdk/components/explorer_item_purchase_result.proto @@ -13,11 +13,11 @@ message PBExplorerItemPurchaseResult { message Failed {} string urn = 1; - uint32 timestamp = 2; + uint32 timestamp = 2; // The scene tick when the event occurred oneof status { Purchased purchased = 10; Dismissed dismissed = 11; Failed failed = 12; } -} \ No newline at end of file +} diff --git a/public/sdk-components.proto b/public/sdk-components.proto index 4f1216cd..be9dec18 100644 --- a/public/sdk-components.proto +++ b/public/sdk-components.proto @@ -14,6 +14,7 @@ import public "decentraland/sdk/components/billboard.proto"; import public "decentraland/sdk/components/camera_mode_area.proto"; import public "decentraland/sdk/components/camera_mode.proto"; import public "decentraland/sdk/components/engine_info.proto"; +import public "decentraland/sdk/components/explorer_item_purchase_result.proto"; import public "decentraland/sdk/components/explorer_ui_events_result.proto"; import public "decentraland/sdk/components/gltf_container.proto"; import public "decentraland/sdk/components/gltf_node_modifiers.proto"; @@ -44,4 +45,3 @@ import public "decentraland/sdk/components/ui_text.proto"; import public "decentraland/sdk/components/ui_transform.proto"; import public "decentraland/sdk/components/video_player.proto"; import public "decentraland/sdk/components/visibility_component.proto"; -import public "decentraland/sdk/components/explorer_item_purchase_event_result.proto"; From 1a785845c4ec047996007964ce3bad85dc7e5e61 Mon Sep 17 00:00:00 2001 From: Vitaly Popuzin Date: Wed, 19 Aug 2026 13:50:00 +0200 Subject: [PATCH 4/8] feat: correlate OpenExplorerUi requests with their result events Adds an optional request_id to OpenExplorerUiRequest, echoed back in PBExplorerUiEventsResult (1220) and PBExplorerItemPurchaseResult (1221). Today a scene cannot tell which events belong to which openExplorerUi call. The explorer knows the pairing -- opened and closed are emitted from a single await scope of one call -- but it is lost at the wire boundary, so SDK helpers have to guess it from timestamp windows and per-panel FIFO matching. The scene sets the id before sending the request, so events that arrive before the RPC response resolves are still correlatable. 0 is the proto3 default and means uncorrelated, which keeps the change additive for clients that do not implement it. This is not the transaction_id deferred earlier in review: that one is about purchase semantics, this one is about binding a call to its own event stream. --- proto/decentraland/kernel/apis/restricted_actions.proto | 3 +++ .../sdk/components/explorer_item_purchase_result.proto | 3 ++- .../sdk/components/explorer_ui_events_result.proto | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/proto/decentraland/kernel/apis/restricted_actions.proto b/proto/decentraland/kernel/apis/restricted_actions.proto index dd4249f7..3babdf99 100644 --- a/proto/decentraland/kernel/apis/restricted_actions.proto +++ b/proto/decentraland/kernel/apis/restricted_actions.proto @@ -78,6 +78,9 @@ enum OpenExplorerUiResult { message OpenExplorerUiRequest { decentraland.sdk.components.common.ExplorerUi ui = 1; + // Echoed back in the events this request produces, so they can be matched to their call. + // 0 means no correlation. + uint32 request_id = 2; message PurchaseParams { string urn = 1; diff --git a/proto/decentraland/sdk/components/explorer_item_purchase_result.proto b/proto/decentraland/sdk/components/explorer_item_purchase_result.proto index 46e0a8dd..110e94dd 100644 --- a/proto/decentraland/sdk/components/explorer_item_purchase_result.proto +++ b/proto/decentraland/sdk/components/explorer_item_purchase_result.proto @@ -14,7 +14,8 @@ message PBExplorerItemPurchaseResult { string urn = 1; uint32 timestamp = 2; // The scene tick when the event occurred - + uint32 request_id = 3; // The OpenExplorerUiRequest.request_id that caused this event; 0 if unset + oneof status { Purchased purchased = 10; Dismissed dismissed = 11; diff --git a/proto/decentraland/sdk/components/explorer_ui_events_result.proto b/proto/decentraland/sdk/components/explorer_ui_events_result.proto index 5c3ea859..4a683d94 100644 --- a/proto/decentraland/sdk/components/explorer_ui_events_result.proto +++ b/proto/decentraland/sdk/components/explorer_ui_events_result.proto @@ -18,6 +18,7 @@ message PBExplorerUiEventsResult { decentraland.sdk.components.common.ExplorerUi ui = 1; // The panel that the event refers to uint32 timestamp = 2; // The scene tick when the event occurred + uint32 request_id = 3; // The OpenExplorerUiRequest.request_id that caused this event; 0 if unset // Extension point, deliberately narrow: a variant belongs here only if it is meaningful for every // value of `ui`, because `ui` and `event` are independent axes and every combination of the two is From ca6849b4cd7d45f42085e722001d7234dba0301b Mon Sep 17 00:00:00 2001 From: Vitaly Popuzin Date: Wed, 19 Aug 2026 14:51:09 +0200 Subject: [PATCH 5/8] fix: make OpenExplorerUiRequest.request_id optional A proto3 scalar without `optional` generates a required TypeScript field, so `openExplorerUi({ ui })` would stop compiling for every existing caller -- including scenes already on @dcl/sdk@next. Explicit presence keeps the change additive and lets the SDK omit the field instead of sending a sentinel. The result components keep the plain uint32: scenes only read those, and 0 reads better there than an undefined check at every access. Co-Authored-By: Claude --- proto/decentraland/kernel/apis/restricted_actions.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proto/decentraland/kernel/apis/restricted_actions.proto b/proto/decentraland/kernel/apis/restricted_actions.proto index 3babdf99..01d16d14 100644 --- a/proto/decentraland/kernel/apis/restricted_actions.proto +++ b/proto/decentraland/kernel/apis/restricted_actions.proto @@ -79,8 +79,8 @@ enum OpenExplorerUiResult { message OpenExplorerUiRequest { decentraland.sdk.components.common.ExplorerUi ui = 1; // Echoed back in the events this request produces, so they can be matched to their call. - // 0 means no correlation. - uint32 request_id = 2; + // Unset means no correlation. + optional uint32 request_id = 2; message PurchaseParams { string urn = 1; From bffcf53dd07a1e4cbbdd8a2f19f379ab6e64d289 Mon Sep 17 00:00:00 2001 From: Vitaly Popuzin Date: Wed, 19 Aug 2026 15:27:07 +0200 Subject: [PATCH 6/8] fix: allow concurrent panels and correct the EU_ITEM_PURCHASE comment EU_ITEM_PURCHASE is a popup, not a fullscreen panel, so it can be shown on top of an already open one. WAS_ALREADY_OPEN now means the requested panel is open rather than any panel, which is what makes concurrent sessions expressible -- and what makes request_id load bearing instead of a nicety. Also points the purchase outcome at 1221; 1220 is the component the line above it describes. Co-Authored-By: Claude --- proto/decentraland/kernel/apis/restricted_actions.proto | 2 +- proto/decentraland/sdk/components/common/explorer_ui.proto | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/proto/decentraland/kernel/apis/restricted_actions.proto b/proto/decentraland/kernel/apis/restricted_actions.proto index 01d16d14..044b7ba8 100644 --- a/proto/decentraland/kernel/apis/restricted_actions.proto +++ b/proto/decentraland/kernel/apis/restricted_actions.proto @@ -70,7 +70,7 @@ message StopEmoteRequest { } enum OpenExplorerUiResult { UNSPECIFIED = 0; OPENED = 1; - WAS_ALREADY_OPEN = 2; // a panel is already open + WAS_ALREADY_OPEN = 2; // the requested panel is already open; other panels may be open too REJECTED_NOT_CURRENT_SCENE = 3; // the standard restricted-actions current-scene gate rejected the call REJECTED_FEATURE_DISABLED = 4; // the requested section is hidden by feature flags or client doesn't have that feature REJECTED_NO_USER_GESTURE = 5; // rejected: the call did not originate from a user gesture diff --git a/proto/decentraland/sdk/components/common/explorer_ui.proto b/proto/decentraland/sdk/components/common/explorer_ui.proto index 473af3fa..cf26cdbe 100644 --- a/proto/decentraland/sdk/components/common/explorer_ui.proto +++ b/proto/decentraland/sdk/components/common/explorer_ui.proto @@ -1,7 +1,8 @@ syntax = "proto3"; package decentraland.sdk.components.common; -// Identifies which fullscreen explorer panel OpenExplorerUi targets. +// Identifies which explorer panel OpenExplorerUi targets. Panels are not all fullscreen: +// EU_ITEM_PURCHASE is a popup and can be shown while another panel is open. // EU_SETTINGS holds 0 so an unset `ui` field defaults to the least-intrusive panel. enum ExplorerUi { EU_SETTINGS = 0; @@ -12,7 +13,7 @@ enum ExplorerUi { EU_PLACES = 5; EU_EVENTS = 6; // Opens the item-purchase confirmation modal for the urn in PurchaseParams. - // UiOpened/UiClosed are emitted in PBExplorerUiEventsResult like for every other value; - // the purchase outcome is reported via the dedicated result component (1220). + // UiOpened/UiClosed are emitted in PBExplorerUiEventsResult like for every other value; + // the purchase outcome is reported via the dedicated result component (1221). EU_ITEM_PURCHASE = 7; } From 9012a1fb20d54c46f7859d8e51d713335fa26441 Mon Sep 17 00:00:00 2001 From: Vitaly Popuzin Date: Wed, 19 Aug 2026 17:34:47 +0200 Subject: [PATCH 7/8] corrected comment --- proto/decentraland/sdk/components/common/explorer_ui.proto | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/proto/decentraland/sdk/components/common/explorer_ui.proto b/proto/decentraland/sdk/components/common/explorer_ui.proto index cf26cdbe..978f0138 100644 --- a/proto/decentraland/sdk/components/common/explorer_ui.proto +++ b/proto/decentraland/sdk/components/common/explorer_ui.proto @@ -1,8 +1,7 @@ syntax = "proto3"; package decentraland.sdk.components.common; -// Identifies which explorer panel OpenExplorerUi targets. Panels are not all fullscreen: -// EU_ITEM_PURCHASE is a popup and can be shown while another panel is open. +// Identifies which explorer panel OpenExplorerUi targets. Panels are not all fullscreen. // EU_SETTINGS holds 0 so an unset `ui` field defaults to the least-intrusive panel. enum ExplorerUi { EU_SETTINGS = 0; From b89cf79a9022675327d186033db4286d3ae1bfde Mon Sep 17 00:00:00 2001 From: Vitaly Popuzin Date: Wed, 19 Aug 2026 18:25:59 +0200 Subject: [PATCH 8/8] docs: say why UiOpened exists next to the OpenExplorerUi verdict The two look redundant when read from the caller's side, which invites removing one of them. They are not: the response is point to point, the component is a scene wide stream. Co-Authored-By: Claude --- .../sdk/components/explorer_ui_events_result.proto | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proto/decentraland/sdk/components/explorer_ui_events_result.proto b/proto/decentraland/sdk/components/explorer_ui_events_result.proto index 4a683d94..5d1df67e 100644 --- a/proto/decentraland/sdk/components/explorer_ui_events_result.proto +++ b/proto/decentraland/sdk/components/explorer_ui_events_result.proto @@ -10,7 +10,8 @@ option (common.ecs_component_id) = 1220; // opened, a panel was closed. It is a grow only value set appended to the scene root entity, so every // event of a tick is delivered and none overwrites another. message PBExplorerUiEventsResult { - // Emitted when an explorer panel is opened. + // Emitted when an explorer panel is opened. The OpenExplorerUi response carries the same fact, + // but it reaches only the caller; this channel is scene wide. message UiOpened {} // Emitted when an explorer panel is closed.