@frak-labs/core-sdk 0.1.0-beta.afa252b0 → 0.1.0-beta.b0bd1f8a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/cdn/bundle.iife.js +14 -0
  2. package/dist/actions-CEEObPYc.js +1 -0
  3. package/dist/actions-DbQhWYx8.cjs +1 -0
  4. package/dist/actions.cjs +1 -1
  5. package/dist/actions.d.cts +3 -1481
  6. package/dist/actions.d.ts +3 -1481
  7. package/dist/actions.js +1 -1
  8. package/dist/bundle.cjs +1 -13
  9. package/dist/bundle.d.cts +6 -2087
  10. package/dist/bundle.d.ts +6 -2087
  11. package/dist/bundle.js +1 -13
  12. package/dist/index-7OZ39x1U.d.ts +195 -0
  13. package/dist/index-C6FxkWPC.d.cts +511 -0
  14. package/dist/index-UFX7xCg3.d.ts +351 -0
  15. package/dist/index-d8xS4ryI.d.ts +511 -0
  16. package/dist/index-p4FqSp8z.d.cts +351 -0
  17. package/dist/index-zDq-VlKx.d.cts +195 -0
  18. package/dist/index.cjs +1 -13
  19. package/dist/index.d.cts +4 -1387
  20. package/dist/index.d.ts +4 -1387
  21. package/dist/index.js +1 -13
  22. package/dist/interaction-DMJ3ZfaF.d.cts +45 -0
  23. package/dist/interaction-KX1h9a7V.d.ts +45 -0
  24. package/dist/interactions-DnfM3oe0.js +1 -0
  25. package/dist/interactions-EIXhNLf6.cjs +1 -0
  26. package/dist/interactions.cjs +1 -1
  27. package/dist/interactions.d.cts +2 -182
  28. package/dist/interactions.d.ts +2 -182
  29. package/dist/interactions.js +1 -1
  30. package/dist/openSso-D--Airj6.d.cts +1018 -0
  31. package/dist/openSso-DsKJ4y0j.d.ts +1018 -0
  32. package/dist/productTypes-BUkXJKZ7.cjs +1 -0
  33. package/dist/productTypes-CGb1MmBF.js +1 -0
  34. package/dist/src-B_xO0AR6.cjs +13 -0
  35. package/dist/src-D2d52OZa.js +13 -0
  36. package/dist/trackEvent-CHnYa85W.js +1 -0
  37. package/dist/trackEvent-GuQm_1Nm.cjs +1 -0
  38. package/package.json +23 -18
  39. package/src/actions/displayEmbeddedWallet.test.ts +194 -0
  40. package/src/actions/displayEmbeddedWallet.ts +20 -0
  41. package/src/actions/displayModal.test.ts +387 -0
  42. package/src/actions/displayModal.ts +131 -0
  43. package/src/actions/getProductInformation.test.ts +133 -0
  44. package/src/actions/getProductInformation.ts +14 -0
  45. package/src/actions/index.ts +29 -0
  46. package/src/actions/openSso.test.ts +407 -0
  47. package/src/actions/openSso.ts +116 -0
  48. package/src/actions/prepareSso.test.ts +223 -0
  49. package/src/actions/prepareSso.ts +48 -0
  50. package/src/actions/referral/processReferral.ts +230 -0
  51. package/src/actions/referral/referralInteraction.ts +57 -0
  52. package/src/actions/sendInteraction.test.ts +219 -0
  53. package/src/actions/sendInteraction.ts +32 -0
  54. package/src/actions/trackPurchaseStatus.test.ts +287 -0
  55. package/src/actions/trackPurchaseStatus.ts +53 -0
  56. package/src/actions/watchWalletStatus.test.ts +372 -0
  57. package/src/actions/watchWalletStatus.ts +94 -0
  58. package/src/actions/wrapper/modalBuilder.ts +212 -0
  59. package/src/actions/wrapper/sendTransaction.ts +62 -0
  60. package/src/actions/wrapper/siweAuthenticate.ts +94 -0
  61. package/src/bundle.ts +3 -0
  62. package/src/clients/DebugInfo.ts +182 -0
  63. package/src/clients/createIFrameFrakClient.ts +287 -0
  64. package/src/clients/index.ts +3 -0
  65. package/src/clients/setupClient.test.ts +343 -0
  66. package/src/clients/setupClient.ts +73 -0
  67. package/src/clients/transports/iframeLifecycleManager.test.ts +399 -0
  68. package/src/clients/transports/iframeLifecycleManager.ts +90 -0
  69. package/src/constants/interactionTypes.ts +44 -0
  70. package/src/constants/locales.ts +14 -0
  71. package/src/constants/productTypes.ts +33 -0
  72. package/src/index.ts +101 -0
  73. package/src/interactions/index.ts +5 -0
  74. package/src/interactions/pressEncoder.test.ts +215 -0
  75. package/src/interactions/pressEncoder.ts +53 -0
  76. package/src/interactions/purchaseEncoder.test.ts +291 -0
  77. package/src/interactions/purchaseEncoder.ts +99 -0
  78. package/src/interactions/referralEncoder.test.ts +170 -0
  79. package/src/interactions/referralEncoder.ts +47 -0
  80. package/src/interactions/retailEncoder.test.ts +107 -0
  81. package/src/interactions/retailEncoder.ts +37 -0
  82. package/src/interactions/webshopEncoder.test.ts +56 -0
  83. package/src/interactions/webshopEncoder.ts +30 -0
  84. package/src/types/client.ts +14 -0
  85. package/src/types/compression.ts +22 -0
  86. package/src/types/config.ts +111 -0
  87. package/src/types/context.ts +13 -0
  88. package/src/types/index.ts +71 -0
  89. package/src/types/lifecycle/client.ts +46 -0
  90. package/src/types/lifecycle/iframe.ts +35 -0
  91. package/src/types/lifecycle/index.ts +2 -0
  92. package/src/types/rpc/displayModal.ts +84 -0
  93. package/src/types/rpc/embedded/index.ts +68 -0
  94. package/src/types/rpc/embedded/loggedIn.ts +55 -0
  95. package/src/types/rpc/embedded/loggedOut.ts +28 -0
  96. package/src/types/rpc/interaction.ts +43 -0
  97. package/src/types/rpc/modal/final.ts +46 -0
  98. package/src/types/rpc/modal/generic.ts +46 -0
  99. package/src/types/rpc/modal/index.ts +20 -0
  100. package/src/types/rpc/modal/login.ts +32 -0
  101. package/src/types/rpc/modal/openSession.ts +25 -0
  102. package/src/types/rpc/modal/siweAuthenticate.ts +37 -0
  103. package/src/types/rpc/modal/transaction.ts +33 -0
  104. package/src/types/rpc/productInformation.ts +59 -0
  105. package/src/types/rpc/sso.ts +80 -0
  106. package/src/types/rpc/walletStatus.ts +35 -0
  107. package/src/types/rpc.ts +158 -0
  108. package/src/types/transport.ts +34 -0
  109. package/src/utils/FrakContext.test.ts +407 -0
  110. package/src/utils/FrakContext.ts +158 -0
  111. package/src/utils/compression/b64.test.ts +181 -0
  112. package/src/utils/compression/b64.ts +29 -0
  113. package/src/utils/compression/compress.test.ts +123 -0
  114. package/src/utils/compression/compress.ts +11 -0
  115. package/src/utils/compression/decompress.test.ts +145 -0
  116. package/src/utils/compression/decompress.ts +11 -0
  117. package/src/utils/compression/index.ts +3 -0
  118. package/src/utils/computeProductId.test.ts +80 -0
  119. package/src/utils/computeProductId.ts +11 -0
  120. package/src/utils/constants.test.ts +23 -0
  121. package/src/utils/constants.ts +4 -0
  122. package/src/utils/formatAmount.test.ts +113 -0
  123. package/src/utils/formatAmount.ts +18 -0
  124. package/src/utils/getCurrencyAmountKey.test.ts +44 -0
  125. package/src/utils/getCurrencyAmountKey.ts +15 -0
  126. package/src/utils/getSupportedCurrency.test.ts +51 -0
  127. package/src/utils/getSupportedCurrency.ts +14 -0
  128. package/src/utils/getSupportedLocale.test.ts +64 -0
  129. package/src/utils/getSupportedLocale.ts +16 -0
  130. package/src/utils/iframeHelper.test.ts +450 -0
  131. package/src/utils/iframeHelper.ts +143 -0
  132. package/src/utils/index.ts +21 -0
  133. package/src/utils/sso.test.ts +361 -0
  134. package/src/utils/sso.ts +119 -0
  135. package/src/utils/ssoUrlListener.ts +60 -0
  136. package/src/utils/trackEvent.test.ts +162 -0
  137. package/src/utils/trackEvent.ts +26 -0
  138. package/cdn/bundle.js +0 -19
  139. package/cdn/bundle.js.LICENSE.txt +0 -10
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Tests for PressInteractionEncoder
3
+ * Tests encoding of press-related user interactions
4
+ */
5
+
6
+ import { pad, toHex } from "viem";
7
+ import { describe, expect, it, test } from "../../tests/vitest-fixtures";
8
+ import { interactionTypes } from "../constants/interactionTypes";
9
+ import { productTypes } from "../constants/productTypes";
10
+ import { PressInteractionEncoder } from "./pressEncoder";
11
+
12
+ describe("PressInteractionEncoder", () => {
13
+ describe("openArticle", () => {
14
+ test("should encode open article interaction with correct structure", ({
15
+ mockArticleId,
16
+ }) => {
17
+ const interaction = PressInteractionEncoder.openArticle({
18
+ articleId: mockArticleId,
19
+ });
20
+
21
+ // Should return PreparedInteraction structure
22
+ expect(interaction).toHaveProperty("handlerTypeDenominator");
23
+ expect(interaction).toHaveProperty("interactionData");
24
+ });
25
+
26
+ test("should use press product type in handlerTypeDenominator", ({
27
+ mockArticleId,
28
+ }) => {
29
+ const interaction = PressInteractionEncoder.openArticle({
30
+ articleId: mockArticleId,
31
+ });
32
+
33
+ // Should use press product type (2)
34
+ const expectedDenominator = toHex(productTypes.press);
35
+ expect(interaction.handlerTypeDenominator).toBe(
36
+ expectedDenominator
37
+ );
38
+ });
39
+
40
+ test("should include openArticle interaction type in data", ({
41
+ mockArticleId,
42
+ }) => {
43
+ const interaction = PressInteractionEncoder.openArticle({
44
+ articleId: mockArticleId,
45
+ });
46
+
47
+ // Should start with openArticle interaction type
48
+ expect(interaction.interactionData).toContain(
49
+ interactionTypes.press.openArticle
50
+ );
51
+ });
52
+
53
+ test("should pad article ID to 32 bytes", ({ mockArticleId }) => {
54
+ const interaction = PressInteractionEncoder.openArticle({
55
+ articleId: mockArticleId,
56
+ });
57
+
58
+ // Article ID should be padded to 32 bytes (64 hex chars + 0x prefix = 66 chars)
59
+ const paddedArticleId = pad(mockArticleId, { size: 32 });
60
+ expect(interaction.interactionData).toContain(
61
+ paddedArticleId.slice(2)
62
+ );
63
+ });
64
+
65
+ it("should handle short article IDs by padding", () => {
66
+ const shortArticleId = "0x01" as const;
67
+ const interaction = PressInteractionEncoder.openArticle({
68
+ articleId: shortArticleId,
69
+ });
70
+
71
+ // Should pad short IDs to 32 bytes
72
+ expect(interaction.interactionData).toBeDefined();
73
+ // Total length: openArticle (10 chars) + padded ID (64 chars) + 0x (2 chars) = 76 chars
74
+ expect(interaction.interactionData.length).toBeGreaterThan(64);
75
+ });
76
+
77
+ test("should produce consistent output for same article ID", ({
78
+ mockArticleId,
79
+ }) => {
80
+ const interaction1 = PressInteractionEncoder.openArticle({
81
+ articleId: mockArticleId,
82
+ });
83
+ const interaction2 = PressInteractionEncoder.openArticle({
84
+ articleId: mockArticleId,
85
+ });
86
+
87
+ // Same input should produce same output
88
+ expect(interaction1).toEqual(interaction2);
89
+ });
90
+ });
91
+
92
+ describe("readArticle", () => {
93
+ test("should encode read article interaction with correct structure", ({
94
+ mockArticleId,
95
+ }) => {
96
+ const interaction = PressInteractionEncoder.readArticle({
97
+ articleId: mockArticleId,
98
+ });
99
+
100
+ // Should return PreparedInteraction structure
101
+ expect(interaction).toHaveProperty("handlerTypeDenominator");
102
+ expect(interaction).toHaveProperty("interactionData");
103
+ });
104
+
105
+ test("should use press product type in handlerTypeDenominator", ({
106
+ mockArticleId,
107
+ }) => {
108
+ const interaction = PressInteractionEncoder.readArticle({
109
+ articleId: mockArticleId,
110
+ });
111
+
112
+ // Should use press product type (2)
113
+ const expectedDenominator = toHex(productTypes.press);
114
+ expect(interaction.handlerTypeDenominator).toBe(
115
+ expectedDenominator
116
+ );
117
+ });
118
+
119
+ test("should include readArticle interaction type in data", ({
120
+ mockArticleId,
121
+ }) => {
122
+ const interaction = PressInteractionEncoder.readArticle({
123
+ articleId: mockArticleId,
124
+ });
125
+
126
+ // Should start with readArticle interaction type
127
+ expect(interaction.interactionData).toContain(
128
+ interactionTypes.press.readArticle
129
+ );
130
+ });
131
+
132
+ test("should pad article ID to 32 bytes", ({ mockArticleId }) => {
133
+ const interaction = PressInteractionEncoder.readArticle({
134
+ articleId: mockArticleId,
135
+ });
136
+
137
+ // Article ID should be padded to 32 bytes
138
+ const paddedArticleId = pad(mockArticleId, { size: 32 });
139
+ expect(interaction.interactionData).toContain(
140
+ paddedArticleId.slice(2)
141
+ );
142
+ });
143
+
144
+ test("should produce different output than openArticle for same ID", ({
145
+ mockArticleId,
146
+ }) => {
147
+ const openInteraction = PressInteractionEncoder.openArticle({
148
+ articleId: mockArticleId,
149
+ });
150
+ const readInteraction = PressInteractionEncoder.readArticle({
151
+ articleId: mockArticleId,
152
+ });
153
+
154
+ // Different interaction types should produce different data
155
+ expect(openInteraction.interactionData).not.toBe(
156
+ readInteraction.interactionData
157
+ );
158
+
159
+ // But should have same handlerTypeDenominator (both press)
160
+ expect(openInteraction.handlerTypeDenominator).toBe(
161
+ readInteraction.handlerTypeDenominator
162
+ );
163
+ });
164
+ });
165
+
166
+ describe("interaction data format", () => {
167
+ test("should concatenate interaction type and padded article ID", ({
168
+ mockArticleId,
169
+ }) => {
170
+ const interaction = PressInteractionEncoder.openArticle({
171
+ articleId: mockArticleId,
172
+ });
173
+
174
+ // Should start with interaction type (10 chars including 0x)
175
+ expect(interaction.interactionData.slice(0, 10)).toBe(
176
+ interactionTypes.press.openArticle
177
+ );
178
+
179
+ // Should be followed by padded article ID (64 hex chars)
180
+ expect(interaction.interactionData.length).toBe(74); // 0x + 8 (type) + 64 (padded ID)
181
+ });
182
+
183
+ test("should produce valid hex strings", ({ mockArticleId }) => {
184
+ const openInteraction = PressInteractionEncoder.openArticle({
185
+ articleId: mockArticleId,
186
+ });
187
+ const readInteraction = PressInteractionEncoder.readArticle({
188
+ articleId: mockArticleId,
189
+ });
190
+
191
+ // Both should be valid hex strings starting with 0x
192
+ expect(openInteraction.interactionData).toMatch(/^0x[0-9a-f]+$/);
193
+ expect(readInteraction.interactionData).toMatch(/^0x[0-9a-f]+$/);
194
+ });
195
+
196
+ it("should handle different article IDs correctly", () => {
197
+ const articleId1 =
198
+ "0x0000000000000000000000000000000000000000000000000000000000000001" as const;
199
+ const articleId2 =
200
+ "0x0000000000000000000000000000000000000000000000000000000000000002" as const;
201
+
202
+ const interaction1 = PressInteractionEncoder.openArticle({
203
+ articleId: articleId1,
204
+ });
205
+ const interaction2 = PressInteractionEncoder.openArticle({
206
+ articleId: articleId2,
207
+ });
208
+
209
+ // Different article IDs should produce different interaction data
210
+ expect(interaction1.interactionData).not.toBe(
211
+ interaction2.interactionData
212
+ );
213
+ });
214
+ });
215
+ });
@@ -0,0 +1,53 @@
1
+ import { concatHex, type Hex, pad, toHex } from "viem";
2
+ import { interactionTypes } from "../constants/interactionTypes";
3
+ import { productTypes } from "../constants/productTypes";
4
+ import type { PreparedInteraction } from "../types";
5
+
6
+ /**
7
+ * Press interactions allow you to track user engagement with articles or other press content on your platform.
8
+ * After setting up these interactions, you can create acquisition campaign based on the user engagement with your press content.
9
+ *
10
+ * :::info
11
+ * To properly handle press interactions, ensure that the "Press" product type is enabled in your Business dashboard.
12
+ * :::
13
+ *
14
+ * @description Encode press related user interactions
15
+ *
16
+ * @group Interactions Encoder
17
+ *
18
+ * @see {@link PreparedInteraction} The prepared interaction object that can be sent
19
+ * @see {@link !actions.sendInteraction | `sendInteraction()`} Action used to send the prepared interaction to the Frak Wallet
20
+ */
21
+ export const PressInteractionEncoder = {
22
+ /**
23
+ * Encode an open article interaction
24
+ * @param args
25
+ * @param args.articleId - The id of the article the user opened (32 bytes), could be a `keccak256` hash of the article slug, or your internal id
26
+ */
27
+ openArticle({ articleId }: { articleId: Hex }): PreparedInteraction {
28
+ const interactionData = concatHex([
29
+ interactionTypes.press.openArticle,
30
+ pad(articleId, { size: 32 }),
31
+ ]);
32
+ return {
33
+ handlerTypeDenominator: toHex(productTypes.press),
34
+ interactionData,
35
+ };
36
+ },
37
+
38
+ /**
39
+ * Encode a read article interaction
40
+ * @param args
41
+ * @param args.articleId - The id of the article the user opened (32 bytes), could be a `keccak256` hash of the article slug, or your internal id
42
+ */
43
+ readArticle({ articleId }: { articleId: Hex }): PreparedInteraction {
44
+ const interactionData = concatHex([
45
+ interactionTypes.press.readArticle,
46
+ pad(articleId, { size: 32 }),
47
+ ]);
48
+ return {
49
+ handlerTypeDenominator: toHex(productTypes.press),
50
+ interactionData,
51
+ };
52
+ },
53
+ };
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Tests for PurchaseInteractionEncoder
3
+ * Tests encoding of purchase-related user interactions
4
+ */
5
+
6
+ import type { Hex } from "viem";
7
+ import { encodeAbiParameters, pad, toHex } from "viem";
8
+ import { describe, expect, test } from "../../tests/vitest-fixtures";
9
+ import { interactionTypes } from "../constants/interactionTypes";
10
+ import { productTypes } from "../constants/productTypes";
11
+ import { PurchaseInteractionEncoder } from "./purchaseEncoder";
12
+
13
+ describe("PurchaseInteractionEncoder", () => {
14
+ describe("startPurchase", () => {
15
+ test("should encode start purchase interaction with correct structure", ({
16
+ mockPurchaseId,
17
+ }) => {
18
+ const interaction = PurchaseInteractionEncoder.startPurchase({
19
+ purchaseId: mockPurchaseId,
20
+ });
21
+
22
+ // Should return PreparedInteraction structure
23
+ expect(interaction).toHaveProperty("handlerTypeDenominator");
24
+ expect(interaction).toHaveProperty("interactionData");
25
+ });
26
+
27
+ test("should use purchase product type in handlerTypeDenominator", ({
28
+ mockPurchaseId,
29
+ }) => {
30
+ const interaction = PurchaseInteractionEncoder.startPurchase({
31
+ purchaseId: mockPurchaseId,
32
+ });
33
+
34
+ // Should use purchase product type (31)
35
+ const expectedDenominator = toHex(productTypes.purchase);
36
+ expect(interaction.handlerTypeDenominator).toBe(
37
+ expectedDenominator
38
+ );
39
+ });
40
+
41
+ test("should include started interaction type in data", ({
42
+ mockPurchaseId,
43
+ }) => {
44
+ const interaction = PurchaseInteractionEncoder.startPurchase({
45
+ purchaseId: mockPurchaseId,
46
+ });
47
+
48
+ // Should start with started interaction type
49
+ expect(interaction.interactionData).toContain(
50
+ interactionTypes.purchase.started
51
+ );
52
+ });
53
+
54
+ test("should pad purchase ID to 32 bytes", ({ mockPurchaseId }) => {
55
+ const interaction = PurchaseInteractionEncoder.startPurchase({
56
+ purchaseId: mockPurchaseId,
57
+ });
58
+
59
+ // Purchase ID should be padded to 32 bytes
60
+ const paddedPurchaseId = pad(mockPurchaseId, { size: 32 });
61
+ expect(interaction.interactionData).toContain(
62
+ paddedPurchaseId.slice(2)
63
+ );
64
+ });
65
+
66
+ test("should produce consistent output for same purchase ID", ({
67
+ mockPurchaseId,
68
+ }) => {
69
+ const interaction1 = PurchaseInteractionEncoder.startPurchase({
70
+ purchaseId: mockPurchaseId,
71
+ });
72
+ const interaction2 = PurchaseInteractionEncoder.startPurchase({
73
+ purchaseId: mockPurchaseId,
74
+ });
75
+
76
+ // Same input should produce same output
77
+ expect(interaction1).toEqual(interaction2);
78
+ });
79
+ });
80
+
81
+ describe("completedPurchase", () => {
82
+ test("should encode completed purchase with proof", ({
83
+ mockPurchaseId,
84
+ mockProof,
85
+ }) => {
86
+ const interaction = PurchaseInteractionEncoder.completedPurchase({
87
+ purchaseId: mockPurchaseId,
88
+ proof: mockProof,
89
+ });
90
+
91
+ // Should return PreparedInteraction structure
92
+ expect(interaction).toHaveProperty("handlerTypeDenominator");
93
+ expect(interaction).toHaveProperty("interactionData");
94
+ });
95
+
96
+ test("should use purchase product type in handlerTypeDenominator", ({
97
+ mockPurchaseId,
98
+ mockProof,
99
+ }) => {
100
+ const interaction = PurchaseInteractionEncoder.completedPurchase({
101
+ purchaseId: mockPurchaseId,
102
+ proof: mockProof,
103
+ });
104
+
105
+ // Should use purchase product type (31)
106
+ const expectedDenominator = toHex(productTypes.purchase);
107
+ expect(interaction.handlerTypeDenominator).toBe(
108
+ expectedDenominator
109
+ );
110
+ });
111
+
112
+ test("should include completed interaction type in data", ({
113
+ mockPurchaseId,
114
+ mockProof,
115
+ }) => {
116
+ const interaction = PurchaseInteractionEncoder.completedPurchase({
117
+ purchaseId: mockPurchaseId,
118
+ proof: mockProof,
119
+ });
120
+
121
+ // Should start with completed interaction type
122
+ expect(interaction.interactionData).toContain(
123
+ interactionTypes.purchase.completed
124
+ );
125
+ });
126
+
127
+ test("should use ABI encoding for inner data", ({
128
+ mockPurchaseId,
129
+ mockProof,
130
+ }) => {
131
+ const interaction = PurchaseInteractionEncoder.completedPurchase({
132
+ purchaseId: mockPurchaseId,
133
+ proof: mockProof,
134
+ });
135
+
136
+ // Inner data should be ABI encoded (uint256 + bytes32[])
137
+ const expectedInnerData = encodeAbiParameters(
138
+ [{ type: "uint256" }, { type: "bytes32[]" }],
139
+ [BigInt(mockPurchaseId), mockProof]
140
+ );
141
+ expect(interaction.interactionData).toContain(
142
+ expectedInnerData.slice(2)
143
+ );
144
+ });
145
+
146
+ test("should handle empty proof array", ({ mockPurchaseId }) => {
147
+ const interaction = PurchaseInteractionEncoder.completedPurchase({
148
+ purchaseId: mockPurchaseId,
149
+ proof: [],
150
+ });
151
+
152
+ // Should still encode properly with empty proof
153
+ expect(interaction.interactionData).toBeDefined();
154
+ expect(interaction.handlerTypeDenominator).toBe(
155
+ toHex(productTypes.purchase)
156
+ );
157
+ });
158
+
159
+ test("should handle multiple proof elements", ({ mockPurchaseId }) => {
160
+ const largeProof: Hex[] = [
161
+ "0x0000000000000000000000000000000000000000000000000000000000000001",
162
+ "0x0000000000000000000000000000000000000000000000000000000000000002",
163
+ "0x0000000000000000000000000000000000000000000000000000000000000003",
164
+ "0x0000000000000000000000000000000000000000000000000000000000000004",
165
+ "0x0000000000000000000000000000000000000000000000000000000000000005",
166
+ ];
167
+
168
+ const interaction = PurchaseInteractionEncoder.completedPurchase({
169
+ purchaseId: mockPurchaseId,
170
+ proof: largeProof,
171
+ });
172
+
173
+ // Should handle larger proof arrays
174
+ expect(interaction.interactionData).toBeDefined();
175
+ expect(interaction.handlerTypeDenominator).toBe(
176
+ toHex(productTypes.purchase)
177
+ );
178
+ });
179
+ });
180
+
181
+ describe("unsafeCompletedPurchase", () => {
182
+ test("should encode unsafe completed purchase", ({
183
+ mockPurchaseId,
184
+ }) => {
185
+ const interaction =
186
+ PurchaseInteractionEncoder.unsafeCompletedPurchase({
187
+ purchaseId: mockPurchaseId,
188
+ });
189
+
190
+ // Should return PreparedInteraction structure
191
+ expect(interaction).toHaveProperty("handlerTypeDenominator");
192
+ expect(interaction).toHaveProperty("interactionData");
193
+ });
194
+
195
+ test("should use purchase product type in handlerTypeDenominator", ({
196
+ mockPurchaseId,
197
+ }) => {
198
+ const interaction =
199
+ PurchaseInteractionEncoder.unsafeCompletedPurchase({
200
+ purchaseId: mockPurchaseId,
201
+ });
202
+
203
+ // Should use purchase product type (31)
204
+ const expectedDenominator = toHex(productTypes.purchase);
205
+ expect(interaction.handlerTypeDenominator).toBe(
206
+ expectedDenominator
207
+ );
208
+ });
209
+
210
+ test("should include unsafeCompleted interaction type in data", ({
211
+ mockPurchaseId,
212
+ }) => {
213
+ const interaction =
214
+ PurchaseInteractionEncoder.unsafeCompletedPurchase({
215
+ purchaseId: mockPurchaseId,
216
+ });
217
+
218
+ // Should start with unsafeCompleted interaction type
219
+ expect(interaction.interactionData).toContain(
220
+ interactionTypes.purchase.unsafeCompleted
221
+ );
222
+ });
223
+
224
+ test("should pad purchase ID to 32 bytes", ({ mockPurchaseId }) => {
225
+ const interaction =
226
+ PurchaseInteractionEncoder.unsafeCompletedPurchase({
227
+ purchaseId: mockPurchaseId,
228
+ });
229
+
230
+ // Purchase ID should be padded to 32 bytes
231
+ const paddedPurchaseId = pad(mockPurchaseId, { size: 32 });
232
+ expect(interaction.interactionData).toContain(
233
+ paddedPurchaseId.slice(2)
234
+ );
235
+ });
236
+
237
+ test("should differ from safe completed purchase", ({
238
+ mockPurchaseId,
239
+ }) => {
240
+ const safeInteraction =
241
+ PurchaseInteractionEncoder.completedPurchase({
242
+ purchaseId: mockPurchaseId,
243
+ proof: [],
244
+ });
245
+ const unsafeInteraction =
246
+ PurchaseInteractionEncoder.unsafeCompletedPurchase({
247
+ purchaseId: mockPurchaseId,
248
+ });
249
+
250
+ // Unsafe and safe versions should be different
251
+ expect(unsafeInteraction.interactionData).not.toBe(
252
+ safeInteraction.interactionData
253
+ );
254
+ });
255
+ });
256
+
257
+ describe("interaction data format", () => {
258
+ test("should produce valid hex strings", ({ mockPurchaseId }) => {
259
+ const startInteraction = PurchaseInteractionEncoder.startPurchase({
260
+ purchaseId: mockPurchaseId,
261
+ });
262
+ const unsafeInteraction =
263
+ PurchaseInteractionEncoder.unsafeCompletedPurchase({
264
+ purchaseId: mockPurchaseId,
265
+ });
266
+
267
+ // Both should be valid hex strings starting with 0x
268
+ expect(startInteraction.interactionData).toMatch(/^0x[0-9a-f]+$/);
269
+ expect(unsafeInteraction.interactionData).toMatch(/^0x[0-9a-f]+$/);
270
+ });
271
+
272
+ test("should handle different purchase IDs correctly", () => {
273
+ const purchaseId1 =
274
+ "0x0000000000000000000000000000000000000000000000000000000000000001" as const;
275
+ const purchaseId2 =
276
+ "0x0000000000000000000000000000000000000000000000000000000000000002" as const;
277
+
278
+ const interaction1 = PurchaseInteractionEncoder.startPurchase({
279
+ purchaseId: purchaseId1,
280
+ });
281
+ const interaction2 = PurchaseInteractionEncoder.startPurchase({
282
+ purchaseId: purchaseId2,
283
+ });
284
+
285
+ // Different purchase IDs should produce different interaction data
286
+ expect(interaction1.interactionData).not.toBe(
287
+ interaction2.interactionData
288
+ );
289
+ });
290
+ });
291
+ });
@@ -0,0 +1,99 @@
1
+ import { concatHex, encodeAbiParameters, type Hex, pad, toHex } from "viem";
2
+ import { interactionTypes } from "../constants/interactionTypes";
3
+ import { productTypes } from "../constants/productTypes";
4
+ import type { PreparedInteraction } from "../types";
5
+
6
+ /**
7
+ * Purchase interactions allow you to track user purchases on your platform.
8
+ * After setting up these interactions, you can create acquisition campaign based on the user purchase (starting a new one, completed, or even purchase dropped).
9
+ *
10
+ * :::info
11
+ * To properly handle purchase interactions, ensure that the "Purchase" product type is enabled in your Business dashboard, and that you have set up everything correctly in the `Purchasetracker` section.
12
+ * :::
13
+ *
14
+ * :::note
15
+ * The `purchaseId` is used on both interactions. It can be computed like this:
16
+ *
17
+ * ```ts
18
+ * const purchaseId = keccak256(concatHex([productId, toHex(externalPurchaseId)]));
19
+ * ```
20
+ *
21
+ * With:
22
+ * - `productId`: The id of your product, you can find it in the product dashboard.
23
+ * - `externalPurchaseId`: The id of the purchase in your system (e.g. the shopify `order_id`).
24
+ * :::
25
+ *
26
+ * @description Encode purchase related user interactions
27
+ *
28
+ * @group Interactions Encoder
29
+ *
30
+ * @see {@link !actions.sendInteraction | `sendInteraction()`} Action used to send the prepared interaction to the Frak Wallet
31
+ * @see {@link PreparedInteraction} The prepared interaction object that can be sent
32
+ * @see {@link !actions.trackPurchaseStatus | `trackPurchaseStatus()`} Action that will automatically send the purchase upon completion
33
+ * @see [Purchase Webhooks](/wallet-sdk/references-api/webhook) Webhooks to be implemented on your side to confirm a purchase
34
+ * @see [Purchase Proof](/wallet-sdk/references-api/purchaseProof) Get a merklee proof for the purchase
35
+ */
36
+ export const PurchaseInteractionEncoder = {
37
+ /**
38
+ * Encode a start purchase interaction
39
+ * @param args
40
+ * @param args.purchaseId - The id of the purchase that is being started.
41
+ */
42
+ startPurchase({ purchaseId }: { purchaseId: Hex }): PreparedInteraction {
43
+ const interactionData = concatHex([
44
+ interactionTypes.purchase.started,
45
+ pad(purchaseId, { size: 32 }),
46
+ ]);
47
+ return {
48
+ handlerTypeDenominator: toHex(productTypes.purchase),
49
+ interactionData,
50
+ };
51
+ },
52
+
53
+ /**
54
+ * Encode a complete purchase interaction
55
+ * @param args
56
+ * @param args.purchaseId - The id of the purchase that is being completed.
57
+ * @param args.proof - The merkle proof that the user has completed the purchase (see [Purchase Webhooks](/wallet-sdk/references-api/webhook) for more details).
58
+ */
59
+ completedPurchase({
60
+ purchaseId,
61
+ proof,
62
+ }: {
63
+ purchaseId: Hex;
64
+ proof: Hex[];
65
+ }): PreparedInteraction {
66
+ const innerData = encodeAbiParameters(
67
+ [{ type: "uint256" }, { type: "bytes32[]" }],
68
+ [BigInt(purchaseId), proof]
69
+ );
70
+ const interactionData = concatHex([
71
+ interactionTypes.purchase.completed,
72
+ innerData,
73
+ ]);
74
+ return {
75
+ handlerTypeDenominator: toHex(productTypes.purchase),
76
+ interactionData,
77
+ };
78
+ },
79
+
80
+ /**
81
+ * Encode an unsafe complete purchase interaction (when we can't provide the proof)
82
+ * @param args
83
+ * @param args.purchaseId - The id of the purchase that is being completed.
84
+ */
85
+ unsafeCompletedPurchase({
86
+ purchaseId,
87
+ }: {
88
+ purchaseId: Hex;
89
+ }): PreparedInteraction {
90
+ const interactionData = concatHex([
91
+ interactionTypes.purchase.unsafeCompleted,
92
+ pad(purchaseId, { size: 32 }),
93
+ ]);
94
+ return {
95
+ handlerTypeDenominator: toHex(productTypes.purchase),
96
+ interactionData,
97
+ };
98
+ },
99
+ };