@frak-labs/core-sdk 1.0.2 → 1.1.0-beta.d970d1ee

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 (95) hide show
  1. package/README.md +0 -1
  2. package/cdn/bundle.js +1 -14
  3. package/dist/actions-Arjch8ws.cjs +1 -0
  4. package/dist/actions-CeWbcY36.js +1 -0
  5. package/dist/actions.cjs +1 -1
  6. package/dist/actions.d.cts +2 -2
  7. package/dist/actions.d.ts +2 -2
  8. package/dist/actions.js +1 -1
  9. package/dist/bundle.cjs +1 -1
  10. package/dist/bundle.d.cts +4 -4
  11. package/dist/bundle.d.ts +4 -4
  12. package/dist/bundle.js +1 -1
  13. package/dist/frakContext-DwNmSqqt.cjs +1 -0
  14. package/dist/frakContext-Dy77kBoO.js +1 -0
  15. package/dist/{index-quaxtKRh.d.ts → index-BoQDFjOU.d.ts} +1 -1
  16. package/dist/{index-DzVPSUQq.d.ts → index-C5EmUmCE.d.cts} +248 -352
  17. package/dist/{index-BsBbSMxk.d.cts → index-XSvobRVM.d.cts} +1 -1
  18. package/dist/{index-s1vE3jLz.d.cts → index-YBqvI7U4.d.ts} +248 -352
  19. package/dist/index.cjs +1 -1
  20. package/dist/index.d.cts +3 -3
  21. package/dist/index.d.ts +3 -3
  22. package/dist/index.js +1 -1
  23. package/dist/{openSso-DyUQew2K.d.ts → openSso-BqvIHd8A.d.cts} +12 -17
  24. package/dist/{openSso-rQhLhPbq.d.cts → openSso-mTA1gB4r.d.ts} +12 -17
  25. package/dist/src-CGTfUgRU.js +1 -0
  26. package/dist/src-myx7UFlu.cjs +1 -0
  27. package/package.json +4 -4
  28. package/src/actions/ensureIdentity.ts +3 -3
  29. package/src/actions/openSso.ts +4 -4
  30. package/src/actions/referral/processReferral.test.ts +38 -22
  31. package/src/actions/referral/processReferral.ts +6 -4
  32. package/src/actions/referral/referralInteraction.test.ts +7 -7
  33. package/src/actions/referral/referralInteraction.ts +1 -1
  34. package/src/actions/sendInteraction.ts +1 -1
  35. package/src/actions/trackPurchaseStatus.test.ts +4 -4
  36. package/src/actions/trackPurchaseStatus.ts +3 -3
  37. package/src/actions/wrapper/siweAuthenticate.test.ts +1 -5
  38. package/src/actions/wrapper/siweAuthenticate.ts +25 -1
  39. package/src/clients/createIFrameFrakClient.ts +5 -21
  40. package/src/clients/index.ts +0 -1
  41. package/src/clients/transports/iframeLifecycleManager.test.ts +10 -10
  42. package/src/clients/transports/iframeLifecycleManager.ts +3 -3
  43. package/src/config/index.ts +3 -0
  44. package/src/{utils → config}/sdkConfigStore.ts +1 -1
  45. package/src/{utils/constants.test.ts → constants.test.ts} +1 -6
  46. package/src/constants.ts +15 -0
  47. package/src/context/address.ts +76 -0
  48. package/src/{utils/FrakContext.test.ts → context/frakContext.test.ts} +4 -2
  49. package/src/{utils/FrakContext.ts → context/frakContext.ts} +4 -4
  50. package/src/{utils → context}/frakContextV2Codec.test.ts +1 -1
  51. package/src/{utils → context}/frakContextV2Codec.ts +6 -6
  52. package/src/context/index.ts +6 -0
  53. package/src/index.ts +17 -22
  54. package/src/stubs/rrweb.ts +8 -4
  55. package/src/types/client.ts +0 -3
  56. package/src/types/config.ts +11 -0
  57. package/src/types/index.ts +1 -0
  58. package/src/utils/{deepLinkWithFallback.ts → browser/deepLinkWithFallback.ts} +10 -5
  59. package/src/utils/{inAppBrowser.ts → browser/inAppBrowser.ts} +13 -0
  60. package/src/utils/browser/index.ts +13 -0
  61. package/src/utils/{formatAmount.test.ts → format/formatAmount.test.ts} +1 -1
  62. package/src/utils/{formatAmount.ts → format/formatAmount.ts} +1 -1
  63. package/src/utils/{getCurrencyAmountKey.test.ts → format/getCurrencyAmountKey.test.ts} +2 -2
  64. package/src/utils/{getCurrencyAmountKey.ts → format/getCurrencyAmountKey.ts} +1 -1
  65. package/src/utils/{getSupportedCurrency.test.ts → format/getSupportedCurrency.test.ts} +2 -2
  66. package/src/utils/{getSupportedCurrency.ts → format/getSupportedCurrency.ts} +2 -2
  67. package/src/utils/{getSupportedLocale.test.ts → format/getSupportedLocale.test.ts} +3 -3
  68. package/src/utils/{getSupportedLocale.ts → format/getSupportedLocale.ts} +2 -2
  69. package/src/utils/format/index.ts +4 -0
  70. package/src/utils/{iframeHelper.test.ts → iframe/iframeHelper.test.ts} +3 -3
  71. package/src/utils/{iframeHelper.ts → iframe/iframeHelper.ts} +3 -3
  72. package/src/utils/iframe/index.ts +6 -0
  73. package/src/utils/index.ts +31 -24
  74. package/src/utils/sso/index.ts +6 -0
  75. package/src/utils/{sso.ts → sso/sso.ts} +2 -2
  76. package/dist/actions-DihYM-OG.js +0 -1
  77. package/dist/actions-cYbmqewX.cjs +0 -1
  78. package/dist/sdkConfigStore-BXzz5PlK.js +0 -1
  79. package/dist/sdkConfigStore-DDL_fjYX.cjs +0 -1
  80. package/dist/src-CfxklqLh.cjs +0 -13
  81. package/dist/src-VDUSvqqt.js +0 -13
  82. package/src/clients/DebugInfo.test.ts +0 -418
  83. package/src/clients/DebugInfo.ts +0 -182
  84. package/src/utils/computeLegacyProductId.ts +0 -11
  85. package/src/utils/constants.ts +0 -9
  86. /package/src/{utils → clients}/ssoUrlListener.test.ts +0 -0
  87. /package/src/{utils → clients}/ssoUrlListener.ts +0 -0
  88. /package/src/{utils → config}/backendUrl.test.ts +0 -0
  89. /package/src/{utils → config}/backendUrl.ts +0 -0
  90. /package/src/{utils → config}/clientId.test.ts +0 -0
  91. /package/src/{utils → config}/clientId.ts +0 -0
  92. /package/src/{utils → config}/sdkConfigStore.test.ts +0 -0
  93. /package/src/{utils → context}/mergeAttribution.test.ts +0 -0
  94. /package/src/{utils → context}/mergeAttribution.ts +0 -0
  95. /package/src/utils/{deepLinkWithFallback.test.ts → browser/deepLinkWithFallback.test.ts} +0 -0
@@ -7,18 +7,18 @@ import {
7
7
  test,
8
8
  } from "../../tests/vitest-fixtures";
9
9
 
10
- vi.mock("../utils/clientId", () => ({
10
+ vi.mock("../config/clientId", () => ({
11
11
  getClientId: vi.fn().mockReturnValue("test-client-id"),
12
12
  }));
13
13
 
14
- vi.mock("../utils/sdkConfigStore", () => ({
14
+ vi.mock("../config/sdkConfigStore", () => ({
15
15
  sdkConfigStore: {
16
16
  resolveMerchantId: vi.fn().mockResolvedValue(undefined),
17
17
  },
18
18
  }));
19
19
 
20
- import { getClientId } from "../utils/clientId";
21
- import { sdkConfigStore } from "../utils/sdkConfigStore";
20
+ import { getClientId } from "../config/clientId";
21
+ import { sdkConfigStore } from "../config/sdkConfigStore";
22
22
  import { trackPurchaseStatus } from "./trackPurchaseStatus";
23
23
 
24
24
  describe.sequential("trackPurchaseStatus", () => {
@@ -1,6 +1,6 @@
1
- import { getBackendUrl } from "../utils/backendUrl";
2
- import { getClientId } from "../utils/clientId";
3
- import { sdkConfigStore } from "../utils/sdkConfigStore";
1
+ import { getBackendUrl } from "../config/backendUrl";
2
+ import { getClientId } from "../config/clientId";
3
+ import { sdkConfigStore } from "../config/sdkConfigStore";
4
4
 
5
5
  /**
6
6
  * Function used to track the status of a purchase
@@ -4,10 +4,6 @@ vi.mock("../displayModal", () => ({
4
4
  displayModal: vi.fn(),
5
5
  }));
6
6
 
7
- vi.mock("viem/siwe", () => ({
8
- generateSiweNonce: vi.fn(() => "mock-nonce-123456"),
9
- }));
10
-
11
7
  import type { Address, Hex } from "viem";
12
8
  import { describe, expect, it } from "../../../tests/vitest-fixtures";
13
9
  import type { FrakClient } from "../../types";
@@ -65,7 +61,7 @@ describe("siweAuthenticate", () => {
65
61
  siweAuthenticate: {
66
62
  siwe: expect.objectContaining({
67
63
  domain: "example.com",
68
- nonce: "mock-nonce-123456",
64
+ nonce: expect.stringMatching(/^[0-9a-f]{96}$/),
69
65
  uri: "https://example.com",
70
66
  version: "1",
71
67
  }),
@@ -1,4 +1,3 @@
1
- import { generateSiweNonce } from "viem/siwe";
2
1
  import type {
3
2
  FrakClient,
4
3
  ModalRpcMetadata,
@@ -7,6 +6,31 @@ import type {
7
6
  } from "../../types";
8
7
  import { displayModal } from "../displayModal";
9
8
 
9
+ /**
10
+ * Generate a random EIP-4361-compatible nonce.
11
+ *
12
+ * Matches viem/siwe's `generateSiweNonce` shape (96 alphanumeric chars) but
13
+ * inlined here to avoid pulling viem's runtime utilities into the bundle.
14
+ * Uses `crypto.getRandomValues` when available (browsers, modern Node) and
15
+ * falls back to `Math.random` for the rare environment without WebCrypto.
16
+ */
17
+ function generateSiweNonce(length = 96): string {
18
+ const byteCount = Math.ceil(length / 2);
19
+ let hex = "";
20
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
21
+ const bytes = new Uint8Array(byteCount);
22
+ crypto.getRandomValues(bytes);
23
+ for (let i = 0; i < bytes.length; i++) {
24
+ hex += bytes[i].toString(16).padStart(2, "0");
25
+ }
26
+ } else {
27
+ for (let i = 0; i < byteCount; i++) {
28
+ hex += ((Math.random() * 256) | 0).toString(16).padStart(2, "0");
29
+ }
30
+ }
31
+ return hex.substring(0, length);
32
+ }
33
+
10
34
  /**
11
35
  * Parameter used to directly show a modal used to authenticate with SIWE
12
36
  * @inline
@@ -6,17 +6,16 @@ import {
6
6
  RpcErrorCodes,
7
7
  } from "@frak-labs/frame-connector";
8
8
  import { OpenPanel } from "@openpanel/web";
9
+ import { getClientId } from "../config/clientId";
10
+ import { sdkConfigStore } from "../config/sdkConfigStore";
11
+ import { BACKUP_KEY } from "../constants";
9
12
  import type { FrakLifecycleEvent } from "../types";
10
13
  import type { FrakClient } from "../types/client";
11
14
  import type { FrakWalletSdkConfig } from "../types/config";
12
15
  import type { SdkResolvedConfig } from "../types/resolvedConfig";
13
16
  import type { IFrameRpcSchema } from "../types/rpc";
14
- import { getClientId } from "../utils";
15
17
  import { clearAllCache } from "../utils/cache";
16
- import { BACKUP_KEY } from "../utils/constants";
17
- import { sdkConfigStore } from "../utils/sdkConfigStore";
18
- import { setupSsoUrlListener } from "../utils/ssoUrlListener";
19
- import { DebugInfoGatherer } from "./DebugInfo";
18
+ import { setupSsoUrlListener } from "./ssoUrlListener";
20
19
  import {
21
20
  createIFrameLifecycleManager,
22
21
  type IframeLifecycleManager,
@@ -84,9 +83,6 @@ export function createIFrameFrakClient({
84
83
  // lifecycle manager resolves the `isConnected` promise.
85
84
  const handshakeStartedAt = Date.now();
86
85
 
87
- // Create our debug info gatherer
88
- const debugInfo = new DebugInfoGatherer(config, iframe);
89
-
90
86
  // Validate iframe
91
87
  if (!iframe.contentWindow) {
92
88
  throw new FrakRpcError(
@@ -115,17 +111,6 @@ export function createIFrameFrakClient({
115
111
  return ctx;
116
112
  },
117
113
  },
118
- // Save debug info
119
- {
120
- onRequest(message, ctx) {
121
- debugInfo.setLastRequest(message);
122
- return ctx;
123
- },
124
- onResponse(message, response) {
125
- debugInfo.setLastResponse(message, response);
126
- return response;
127
- },
128
- },
129
114
  ],
130
115
  // Add lifecycle handlers to process iframe lifecycle events
131
116
  lifecycleHandlers: {
@@ -227,7 +212,7 @@ export function createIFrameFrakClient({
227
212
  contextSent,
228
213
  openPanel,
229
214
  })
230
- .then(() => debugInfo.updateSetupStatus(true))
215
+ .then(() => {})
231
216
  .catch((err) => {
232
217
  contextSent.reject(err);
233
218
  throw err;
@@ -235,7 +220,6 @@ export function createIFrameFrakClient({
235
220
 
236
221
  return {
237
222
  config,
238
- debugInfo,
239
223
  waitForConnection: lifecycleManager.isConnected,
240
224
  waitForSetup,
241
225
  request: rpcClient.request,
@@ -1,3 +1,2 @@
1
1
  export { createIFrameFrakClient } from "./createIFrameFrakClient";
2
- export { DebugInfoGatherer } from "./DebugInfo";
3
2
  export { setupClient } from "./setupClient";
@@ -16,19 +16,19 @@ vi.mock("@frak-labs/frame-connector", () => ({
16
16
  },
17
17
  }));
18
18
 
19
- vi.mock("../../utils/constants", () => ({
19
+ vi.mock("../../constants", () => ({
20
20
  BACKUP_KEY: "frak-backup-key",
21
21
  }));
22
22
 
23
- vi.mock("../../utils/iframeHelper", () => ({
23
+ vi.mock("../../utils/iframe/iframeHelper", () => ({
24
24
  changeIframeVisibility: vi.fn(),
25
25
  }));
26
26
 
27
- vi.mock("../../utils/clientId", () => ({
27
+ vi.mock("../../config/clientId", () => ({
28
28
  getClientId: vi.fn(() => "mock-client-id"),
29
29
  }));
30
30
 
31
- vi.mock("../../utils/deepLinkWithFallback", () => ({
31
+ vi.mock("../../utils/browser/deepLinkWithFallback", () => ({
32
32
  isFrakDeepLink: vi.fn((url: string) => url.startsWith("frakwallet://")),
33
33
  triggerDeepLinkWithFallback: vi.fn(),
34
34
  }));
@@ -185,7 +185,7 @@ describe("createIFrameLifecycleManager", () => {
185
185
  "./iframeLifecycleManager"
186
186
  );
187
187
  const { changeIframeVisibility } = await import(
188
- "../../utils/iframeHelper"
188
+ "../../utils/iframe/iframeHelper"
189
189
  );
190
190
 
191
191
  const mockIframe = document.createElement("iframe");
@@ -211,7 +211,7 @@ describe("createIFrameLifecycleManager", () => {
211
211
  "./iframeLifecycleManager"
212
212
  );
213
213
  const { changeIframeVisibility } = await import(
214
- "../../utils/iframeHelper"
214
+ "../../utils/iframe/iframeHelper"
215
215
  );
216
216
 
217
217
  const mockIframe = document.createElement("iframe");
@@ -301,7 +301,7 @@ describe("createIFrameLifecycleManager", () => {
301
301
  "./iframeLifecycleManager"
302
302
  );
303
303
  const { triggerDeepLinkWithFallback } = await import(
304
- "../../utils/deepLinkWithFallback"
304
+ "../../utils/browser/deepLinkWithFallback"
305
305
  );
306
306
 
307
307
  Object.defineProperty(window, "location", {
@@ -340,7 +340,7 @@ describe("createIFrameLifecycleManager", () => {
340
340
  "./iframeLifecycleManager"
341
341
  );
342
342
  const { triggerDeepLinkWithFallback } = await import(
343
- "../../utils/deepLinkWithFallback"
343
+ "../../utils/browser/deepLinkWithFallback"
344
344
  );
345
345
 
346
346
  Object.defineProperty(window, "location", {
@@ -396,7 +396,7 @@ describe("createIFrameLifecycleManager", () => {
396
396
  "./iframeLifecycleManager"
397
397
  );
398
398
  const { triggerDeepLinkWithFallback } = await import(
399
- "../../utils/deepLinkWithFallback"
399
+ "../../utils/browser/deepLinkWithFallback"
400
400
  );
401
401
 
402
402
  Object.defineProperty(window, "location", {
@@ -453,7 +453,7 @@ describe("createIFrameLifecycleManager", () => {
453
453
  "./iframeLifecycleManager"
454
454
  );
455
455
  const { changeIframeVisibility } = await import(
456
- "../../utils/iframeHelper"
456
+ "../../utils/iframe/iframeHelper"
457
457
  );
458
458
 
459
459
  const mockIframe = document.createElement("iframe");
@@ -1,11 +1,11 @@
1
1
  import { Deferred } from "@frak-labs/frame-connector";
2
+ import { BACKUP_KEY } from "../../constants";
2
3
  import type { FrakLifecycleEvent } from "../../types";
3
- import { BACKUP_KEY } from "../../utils/constants";
4
4
  import {
5
5
  isFrakDeepLink,
6
6
  triggerDeepLinkWithFallback,
7
- } from "../../utils/deepLinkWithFallback";
8
- import { changeIframeVisibility } from "../../utils/iframeHelper";
7
+ } from "../../utils/browser/deepLinkWithFallback";
8
+ import { changeIframeVisibility } from "../../utils/iframe/iframeHelper";
9
9
 
10
10
  /**
11
11
  * Detect iOS in-app browsers (Instagram, Facebook) where server-side
@@ -0,0 +1,3 @@
1
+ export { getBackendUrl } from "./backendUrl";
2
+ export { getClientId } from "./clientId";
3
+ export { sdkConfigStore } from "./sdkConfigStore";
@@ -14,8 +14,8 @@ import type {
14
14
  MerchantConfigResponse,
15
15
  SdkResolvedConfig,
16
16
  } from "../types/resolvedConfig";
17
+ import { clearAllCache, withCache } from "../utils/cache";
17
18
  import { getBackendUrl } from "./backendUrl";
18
- import { clearAllCache, withCache } from "./cache";
19
19
 
20
20
  const GLOBAL_KEY = "__frakSdkConfig";
21
21
  const CACHE_TTL = 30_000; // 30 seconds
@@ -1,9 +1,4 @@
1
- /**
2
- * Tests for constants
3
- * Tests backup key constant value
4
- */
5
-
6
- import { describe, expect, it } from "../../tests/vitest-fixtures";
1
+ import { describe, expect, it } from "../tests/vitest-fixtures";
7
2
  import { BACKUP_KEY } from "./constants";
8
3
 
9
4
  describe("constants", () => {
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The backup key for client side backup if needed
3
+ */
4
+ export const BACKUP_KEY = "nexus-wallet-backup";
5
+
6
+ /**
7
+ * Deep link scheme for Frak Wallet mobile app.
8
+ *
9
+ * Replaced at build time via tsdown/Vite `define`. Defaults to the prod scheme;
10
+ * in-monorepo dev builds (listener at wallet-dev.frak.id) override this with
11
+ * `frakwallet-dev://` so deep links open the dev wallet variant (id.frak.wallet.dev).
12
+ * External integrators consuming the published NPM/CDN bundle always see the prod scheme.
13
+ */
14
+ export const DEEP_LINK_SCHEME: string =
15
+ process.env.DEEP_LINK_SCHEME ?? "frakwallet://";
@@ -0,0 +1,76 @@
1
+ import type { Address } from "viem";
2
+
3
+ /**
4
+ * Address utilities — minimal, dependency-free replacements for the subset of
5
+ * `viem` helpers we used to import. Keeping these in-house lets the SDK ship
6
+ * without pulling viem's checksum/keccak/error chain into the bundle.
7
+ *
8
+ * Scope is intentionally narrow:
9
+ * - `isAddress`: shape-only validation (no EIP-55 checksum)
10
+ * - `areAddressesEqual`: case-insensitive equality
11
+ * - `addressToBytes` / `bytesToAddress`: fixed 20-byte conversion
12
+ */
13
+
14
+ /** Matches a 0x-prefixed 40-char hex string regardless of case. */
15
+ const ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/;
16
+
17
+ /**
18
+ * Check whether a value is a syntactically valid Ethereum address.
19
+ *
20
+ * This intentionally skips EIP-55 checksum validation: the SDK never produces
21
+ * checksum-cased payloads, and downstream consumers (wallet, indexer) treat
22
+ * addresses case-insensitively. Avoiding the checksum path drops keccak256 +
23
+ * @noble/hashes from the bundle.
24
+ */
25
+ export function isAddress(value: unknown): value is Address {
26
+ return typeof value === "string" && ADDRESS_REGEX.test(value);
27
+ }
28
+
29
+ /**
30
+ * Case-insensitive equality check for two Ethereum addresses.
31
+ *
32
+ * Both inputs are assumed to be syntactically valid addresses; callers that
33
+ * receive untrusted input should validate via {@link isAddress} first.
34
+ */
35
+ export function areAddressesEqual(a: Address, b: Address): boolean {
36
+ return a.toLowerCase() === b.toLowerCase();
37
+ }
38
+
39
+ /**
40
+ * Decode a 20-byte Ethereum address into a fixed-size Uint8Array(20).
41
+ *
42
+ * Throws when the input is not exactly `0x` + 40 hex chars — callers wrap
43
+ * the call in try/catch (see {@link FrakContextManager.compress}) so any
44
+ * malformed input degrades to a graceful undefined return.
45
+ */
46
+ export function addressToBytes(address: Address): Uint8Array {
47
+ const bytes = new Uint8Array(20);
48
+ for (let i = 0; i < 20; i++) {
49
+ const byte = Number.parseInt(
50
+ address.substring(2 + i * 2, 4 + i * 2),
51
+ 16
52
+ );
53
+ if (Number.isNaN(byte)) {
54
+ throw new Error(`Invalid address: ${address}`);
55
+ }
56
+ bytes[i] = byte;
57
+ }
58
+ return bytes;
59
+ }
60
+
61
+ /** Lookup table avoids `padStart` overhead in the hot encode loop. */
62
+ const HEX_BYTE = /*#__PURE__*/ Array.from({ length: 256 }, (_, i) =>
63
+ i.toString(16).padStart(2, "0")
64
+ );
65
+
66
+ /**
67
+ * Encode a 20-byte Uint8Array (or a 20-byte subarray view) into a lowercase
68
+ * hex Ethereum address. The caller MUST guarantee `bytes.length === 20`.
69
+ */
70
+ export function bytesToAddress(bytes: Uint8Array): Address {
71
+ let out = "0x";
72
+ for (let i = 0; i < 20; i++) {
73
+ out += HEX_BYTE[bytes[i]];
74
+ }
75
+ return out as Address;
76
+ }
@@ -9,7 +9,7 @@ import {
9
9
  vi,
10
10
  } from "../../tests/vitest-fixtures";
11
11
  import type { FrakContextV1, FrakContextV2 } from "../types";
12
- import { FrakContextManager } from "./FrakContext";
12
+ import { FrakContextManager } from "./frakContext";
13
13
 
14
14
  describe("FrakContextManager", () => {
15
15
  let consoleErrorSpy: any;
@@ -144,7 +144,9 @@ describe("FrakContextManager", () => {
144
144
  const { encodeFrakContextV2 } = await import(
145
145
  "./frakContextV2Codec"
146
146
  );
147
- const { base64urlEncode } = await import("./compression/b64");
147
+ const { base64urlEncode } = await import(
148
+ "../utils/compression/b64"
149
+ );
148
150
  const encoded = encodeFrakContextV2(v2Context);
149
151
  expect(encoded).toBeDefined();
150
152
  const tampered = new Uint8Array(encoded as Uint8Array);
@@ -1,4 +1,3 @@
1
- import { type Address, bytesToHex, hexToBytes, isAddress } from "viem";
2
1
  import type {
3
2
  AttributionParams,
4
3
  FrakContext,
@@ -6,7 +5,8 @@ import type {
6
5
  FrakContextV2,
7
6
  } from "../types";
8
7
  import { isV2Context } from "../types";
9
- import { base64urlDecode, base64urlEncode } from "./compression/b64";
8
+ import { base64urlDecode, base64urlEncode } from "../utils/compression/b64";
9
+ import { addressToBytes, bytesToAddress, isAddress } from "./address";
10
10
  import { decodeFrakContextV2, encodeFrakContextV2 } from "./frakContextV2Codec";
11
11
 
12
12
  /**
@@ -34,7 +34,7 @@ function compress(context?: FrakContextV1 | FrakContextV2): string | undefined {
34
34
  }
35
35
 
36
36
  // V1 legacy: compress wallet address as raw bytes
37
- const bytes = hexToBytes(context.r);
37
+ const bytes = addressToBytes(context.r);
38
38
  return base64urlEncode(bytes);
39
39
  } catch (e) {
40
40
  console.error("Error compressing Frak context", { e, context });
@@ -64,7 +64,7 @@ function decompress(context?: string): FrakContext | undefined {
64
64
  return undefined;
65
65
  }
66
66
 
67
- const hex = bytesToHex(bytes, { size: 20 }) as Address;
67
+ const hex = bytesToAddress(bytes);
68
68
  if (isAddress(hex)) {
69
69
  return { r: hex };
70
70
  }
@@ -1,7 +1,7 @@
1
1
  import type { Address } from "viem";
2
2
  import { describe, expect, it } from "../../tests/vitest-fixtures";
3
3
  import type { FrakContextV2 } from "../types";
4
- import { base64urlEncode } from "./compression/b64";
4
+ import { base64urlEncode } from "../utils/compression/b64";
5
5
  import {
6
6
  decodeFrakContextV2,
7
7
  encodeFrakContextV2,
@@ -28,8 +28,9 @@
28
28
  *
29
29
  * @ignore
30
30
  */
31
- import { type Address, bytesToHex, hexToBytes, isAddress } from "viem";
31
+ import type { Address } from "viem";
32
32
  import type { FrakContextV2 } from "../types";
33
+ import { addressToBytes, bytesToAddress, isAddress } from "./address";
33
34
 
34
35
  const VERSION_V2 = 0x02;
35
36
  const VERSION_MASK = 0x0f;
@@ -111,7 +112,7 @@ export function encodeFrakContextV2(ctx: FrakContextV2): Uint8Array | null {
111
112
  }
112
113
 
113
114
  if (hasW) {
114
- buf.set(hexToBytes(ctx.w as Address), offset);
115
+ buf.set(addressToBytes(ctx.w as Address), offset);
115
116
  offset += ADDRESS_BYTES;
116
117
  }
117
118
 
@@ -165,10 +166,9 @@ export function decodeFrakContextV2(buf: Uint8Array): FrakContextV2 | null {
165
166
  }
166
167
 
167
168
  if (hasW) {
168
- const walletHex = bytesToHex(
169
- buf.subarray(offset, offset + ADDRESS_BYTES),
170
- { size: ADDRESS_BYTES }
171
- ) as Address;
169
+ const walletHex = bytesToAddress(
170
+ buf.subarray(offset, offset + ADDRESS_BYTES)
171
+ );
172
172
  if (!isAddress(walletHex)) return null;
173
173
  out.w = walletHex;
174
174
  offset += ADDRESS_BYTES;
@@ -0,0 +1,6 @@
1
+ export { areAddressesEqual } from "./address";
2
+ export { FrakContextManager } from "./frakContext";
3
+ export {
4
+ type MergeAttributionInput,
5
+ mergeAttribution,
6
+ } from "./mergeAttribution";
package/src/index.ts CHANGED
@@ -1,15 +1,22 @@
1
1
  // Clients
2
2
 
3
3
  export { ssoPopupFeatures, ssoPopupName } from "./actions/openSso";
4
+ export { createIFrameFrakClient, setupClient } from "./clients";
5
+ // Config (reactive merchant config + identity)
4
6
  export {
5
- createIFrameFrakClient,
6
- DebugInfoGatherer,
7
- setupClient,
8
- } from "./clients";
9
-
7
+ getBackendUrl,
8
+ getClientId,
9
+ sdkConfigStore,
10
+ } from "./config";
11
+ // Constants
12
+ export { DEEP_LINK_SCHEME } from "./constants";
10
13
  export type { InteractionTypeKey } from "./constants/interactionTypes";
11
- export { type LocalesKey, locales } from "./constants/locales";
12
-
14
+ // Context (FrakContext URL codec + attribution merge)
15
+ export {
16
+ FrakContextManager,
17
+ type MergeAttributionInput,
18
+ mergeAttribution,
19
+ } from "./context";
13
20
  // Types
14
21
  export type {
15
22
  AttributionDefaults,
@@ -48,6 +55,7 @@ export type {
48
55
  // Compression
49
56
  KeyProvider,
50
57
  Language,
58
+ ListenerPreloadOption,
51
59
  LocalizedI18nConfig,
52
60
  LoggedInEmbeddedView,
53
61
  LoggedOutEmbeddedView,
@@ -87,7 +95,7 @@ export type {
87
95
  UtmParams,
88
96
  WalletStatusReturnType,
89
97
  } from "./types";
90
- export { isV1Context, isV2Context } from "./types";
98
+
91
99
  // Utils
92
100
  export {
93
101
  type AppSpecificSsoMetadata,
@@ -97,30 +105,18 @@ export {
97
105
  type CompressedSsoData,
98
106
  clearAllCache,
99
107
  compressJsonToB64,
100
- createIframe,
101
- DEEP_LINK_SCHEME,
102
108
  type DeepLinkFallbackOptions,
103
109
  decompressJsonFromB64,
104
- FrakContextManager,
105
110
  type FullSsoParams,
106
111
  findIframeInOpener,
107
112
  formatAmount,
108
113
  generateSsoUrl,
109
- getBackendUrl,
110
- getCache,
111
- getClientId,
112
114
  getCurrencyAmountKey,
113
115
  getSupportedCurrency,
114
- getSupportedLocale,
115
- isChromiumAndroid,
116
- isFrakDeepLink,
117
116
  isInAppBrowser,
118
117
  isIOS,
119
- type MergeAttributionInput,
120
- mergeAttribution,
118
+ isMobile,
121
119
  redirectToExternalBrowser,
122
- sdkConfigStore,
123
- toAndroidIntentUrl,
124
120
  trackEvent,
125
121
  triggerDeepLinkWithFallback,
126
122
  withCache,
@@ -129,4 +125,3 @@ export type {
129
125
  SdkEventMap,
130
126
  SdkHandshakeFailureReason,
131
127
  } from "./utils/analytics";
132
- export { computeLegacyProductId } from "./utils/computeLegacyProductId";
@@ -1,8 +1,12 @@
1
1
  /**
2
- * Stub for rrweb. The @openpanel/web package statically imports `record` from
3
- * rrweb even when session replay is disabled. This stub replaces the module so
4
- * that rrweb is not included in the bundle.
5
- * @see https://github.com/Openpanel-dev/openpanel/issues/336
2
+ * Stub for rrweb. The IIFE/CDN bundles inline every dependency
3
+ * (alwaysBundle catch-all), which would also pull in rrweb via the dynamic
4
+ * `replay` chunk loaded by @openpanel/web. Session replay is disabled in our
5
+ * SDK, so we alias rrweb to a noop record() to keep the CDN bundle small.
6
+ *
7
+ * The NPM ESM/CJS builds don't need this alias: @openpanel/web 1.4.1+ loads
8
+ * rrweb through a dynamic `import("./replay-…")`, so consumers' bundlers can
9
+ * tree-shake / code-split it on their own.
6
10
  */
7
11
  export function record() {
8
12
  return () => {};
@@ -7,8 +7,5 @@ import type { IFrameTransport } from "./transport";
7
7
  */
8
8
  export type FrakClient = {
9
9
  config: FrakWalletSdkConfig;
10
- debugInfo: {
11
- formatDebugInfo: (error: Error | unknown | string) => string;
12
- };
13
10
  openPanel?: OpenPanel;
14
11
  } & IFrameTransport;
@@ -87,6 +87,11 @@ export type FrakWalletSdkConfig = {
87
87
  * excluded — it is per-content/per-product, never a merchant-wide default.
88
88
  */
89
89
  attribution?: AttributionDefaults;
90
+ /**
91
+ * Preload specific UI views inside the listener iframe for better UX.
92
+ * Default: ["sharing"]
93
+ */
94
+ preload?: ListenerPreloadOption[];
90
95
  };
91
96
 
92
97
  /**
@@ -126,6 +131,12 @@ export type I18nConfig =
126
131
  | Record<Language, LocalizedI18nConfig>
127
132
  | LocalizedI18nConfig;
128
133
 
134
+ /**
135
+ * Options for preloading the listener UI
136
+ * @category Config
137
+ */
138
+ export type ListenerPreloadOption = "modal" | "sharing";
139
+
129
140
  /**
130
141
  * A localized i18n config (inline objects only — URL-based i18n removed)
131
142
  * @category Config
@@ -12,6 +12,7 @@ export type {
12
12
  FrakWalletSdkConfig,
13
13
  I18nConfig,
14
14
  Language,
15
+ ListenerPreloadOption,
15
16
  LocalizedI18nConfig,
16
17
  } from "./config";
17
18
  // Utils
@@ -1,4 +1,4 @@
1
- import { DEEP_LINK_SCHEME } from "./constants";
1
+ import { DEEP_LINK_SCHEME } from "../../constants";
2
2
 
3
3
  /**
4
4
  * Options for deep link with fallback
@@ -24,7 +24,7 @@ export function isChromiumAndroid(): boolean {
24
24
  }
25
25
 
26
26
  /**
27
- * Convert a frakwallet:// deep link to an Android intent:// URL.
27
+ * Convert a Frak deep link to an Android intent:// URL.
28
28
  *
29
29
  * Intent URLs let Chromium browsers open the app directly without
30
30
  * showing the "Continue to app?" confirmation bar.
@@ -35,12 +35,17 @@ export function isChromiumAndroid(): boolean {
35
35
  * Without `package`, Chrome simply does nothing when the app is
36
36
  * missing, allowing the fallback mechanism to fire correctly.
37
37
  *
38
- * Format: intent://path#Intent;scheme=frakwallet;end
38
+ * The scheme is derived from `DEEP_LINK_SCHEME` so the dev variant
39
+ * (`frakwallet-dev://`) routes to the dev app shell, not prod.
40
+ *
41
+ * Format: intent://path#Intent;scheme=<scheme>;end
39
42
  */
43
+ const DEEP_LINK_SCHEME_NAME = DEEP_LINK_SCHEME.replace("://", "");
44
+
40
45
  export function toAndroidIntentUrl(deepLink: string): string {
41
- // Extract everything after "frakwallet://"
46
+ // Extract everything after the scheme (e.g. "frakwallet://" or "frakwallet-dev://")
42
47
  const path = deepLink.slice(DEEP_LINK_SCHEME.length);
43
- return `intent://${path}#Intent;scheme=frakwallet;end`;
48
+ return `intent://${path}#Intent;scheme=${DEEP_LINK_SCHEME_NAME};end`;
44
49
  }
45
50
 
46
51
  /**