diff --git a/SVSim.EmulatedEntrypoint/Services/GiftRewardTypes.cs b/SVSim.EmulatedEntrypoint/Services/GiftRewardTypes.cs
new file mode 100644
index 0000000..3e6099d
--- /dev/null
+++ b/SVSim.EmulatedEntrypoint/Services/GiftRewardTypes.cs
@@ -0,0 +1,42 @@
+using SVSim.Database.Enums;
+
+namespace SVSim.EmulatedEntrypoint.Services;
+
+///
+/// The subset of that IInventoryTransaction.GrantAsync
+/// can grant via the gift-inbox claim flow. Producers (serial codes, future event mailers)
+/// validate against this before creating ViewerPresent rows so unsupported types
+/// fail at production time rather than at the player's claim.
+///
+///
+/// Excluded from gift grants:
+///
+/// - SpotCard, SpotCardOnlyLatestCardPack — InventoryTransaction throws NotSupportedException.
+/// - FreeGachaCount — InventoryTransaction has no grant case (default arm throws NotImplementedException).
+///
+///
+public static class GiftRewardTypes
+{
+ public static bool IsSupported(UserGoodsType type) => type switch
+ {
+ UserGoodsType.Crystal => true,
+ UserGoodsType.Rupy => true,
+ UserGoodsType.RedEther => true,
+ UserGoodsType.SpotCardPoint => true,
+ UserGoodsType.Item => true,
+ UserGoodsType.Card => true,
+ UserGoodsType.Sleeve => true,
+ UserGoodsType.Emblem => true,
+ UserGoodsType.Degree => true,
+ UserGoodsType.Skin => true,
+ UserGoodsType.MyPageBG => true,
+ UserGoodsType.SpotCard => false,
+ UserGoodsType.SpotCardOnlyLatestCardPack => false,
+ UserGoodsType.FreeGachaCount => false,
+ _ => false,
+ };
+
+ public static bool IsSupported(int wireType) =>
+ Enum.IsDefined(typeof(UserGoodsType), wireType) &&
+ IsSupported((UserGoodsType)wireType);
+}
diff --git a/SVSim.UnitTests/Services/GiftRewardTypesTests.cs b/SVSim.UnitTests/Services/GiftRewardTypesTests.cs
new file mode 100644
index 0000000..5524a1f
--- /dev/null
+++ b/SVSim.UnitTests/Services/GiftRewardTypesTests.cs
@@ -0,0 +1,43 @@
+using SVSim.Database.Enums;
+using SVSim.EmulatedEntrypoint.Services;
+
+namespace SVSim.UnitTests.Services;
+
+public class GiftRewardTypesTests
+{
+ [TestCase(UserGoodsType.Crystal)]
+ [TestCase(UserGoodsType.Rupy)]
+ [TestCase(UserGoodsType.RedEther)]
+ [TestCase(UserGoodsType.SpotCardPoint)]
+ [TestCase(UserGoodsType.Item)]
+ [TestCase(UserGoodsType.Card)]
+ [TestCase(UserGoodsType.Sleeve)]
+ [TestCase(UserGoodsType.Emblem)]
+ [TestCase(UserGoodsType.Degree)]
+ [TestCase(UserGoodsType.Skin)]
+ [TestCase(UserGoodsType.MyPageBG)]
+ public void IsSupported_returns_true_for_inventory_service_grant_set(UserGoodsType type)
+ {
+ Assert.That(GiftRewardTypes.IsSupported(type), Is.True);
+ }
+
+ [TestCase(UserGoodsType.SpotCard)]
+ [TestCase(UserGoodsType.SpotCardOnlyLatestCardPack)]
+ [TestCase(UserGoodsType.FreeGachaCount)]
+ public void IsSupported_returns_false_for_inventory_service_unsupported_types(UserGoodsType type)
+ {
+ Assert.That(GiftRewardTypes.IsSupported(type), Is.False);
+ }
+
+ [TestCase(1, ExpectedResult = true)] // RedEther
+ [TestCase(2, ExpectedResult = true)] // Crystal
+ [TestCase(3, ExpectedResult = false)] // gap in UserGoodsType enum
+ [TestCase(4, ExpectedResult = true)] // Item
+ [TestCase(11, ExpectedResult = false)] // SpotCard
+ [TestCase(14, ExpectedResult = false)] // FreeGachaCount
+ [TestCase(15, ExpectedResult = true)] // MyPageBG
+ [TestCase(99, ExpectedResult = false)] // out of range
+ [TestCase(-1, ExpectedResult = false)] // negative
+ public bool IsSupported_int_overload_validates_enum_definition(int wireType) =>
+ GiftRewardTypes.IsSupported(wireType);
+}