@frak-labs/core-sdk 0.2.1-beta.b38eef2e → 0.2.1-beta.d2556d47

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 (59) hide show
  1. package/README.md +1 -2
  2. package/cdn/bundle.js +55 -3
  3. package/dist/actions.cjs +1 -1
  4. package/dist/actions.d.cts +2 -2
  5. package/dist/actions.d.ts +2 -2
  6. package/dist/actions.js +1 -1
  7. package/dist/bundle.cjs +1 -1
  8. package/dist/bundle.d.cts +4 -4
  9. package/dist/bundle.d.ts +4 -4
  10. package/dist/bundle.js +1 -1
  11. package/dist/{computeLegacyProductId-b5cUWdAm.d.ts → computeLegacyProductId-BP-ciVsp.d.cts} +30 -44
  12. package/dist/{computeLegacyProductId-CCAZvLa5.d.cts → computeLegacyProductId-DiJd7RNo.d.ts} +30 -44
  13. package/dist/index.cjs +1 -1
  14. package/dist/index.d.cts +3 -3
  15. package/dist/index.d.ts +3 -3
  16. package/dist/index.js +1 -1
  17. package/dist/{openSso-B0g7-807.d.cts → openSso-B8v3Vtnh.d.ts} +118 -46
  18. package/dist/{openSso-CMzwvaCa.d.ts → openSso-n_B4LSuW.d.cts} +118 -46
  19. package/dist/setupClient-Dr_UYfTD.cjs +13 -0
  20. package/dist/setupClient-TuhDjVJx.js +13 -0
  21. package/dist/siweAuthenticate-0UPcUqI1.js +1 -0
  22. package/dist/{siweAuthenticate-CVigMOxz.d.cts → siweAuthenticate-CDCsp8EJ.d.ts} +8 -5
  23. package/dist/siweAuthenticate-CfQibjZR.cjs +1 -0
  24. package/dist/{siweAuthenticate-CnCZ7mok.d.ts → siweAuthenticate-yITE-iKh.d.cts} +8 -5
  25. package/dist/trackEvent-5j5kkOCj.js +1 -0
  26. package/dist/trackEvent-B2uom25e.cjs +1 -0
  27. package/package.json +8 -8
  28. package/src/actions/displayEmbeddedWallet.ts +6 -2
  29. package/src/actions/displayModal.ts +6 -2
  30. package/src/actions/ensureIdentity.ts +2 -2
  31. package/src/actions/trackPurchaseStatus.test.ts +32 -20
  32. package/src/actions/trackPurchaseStatus.ts +3 -5
  33. package/src/actions/wrapper/modalBuilder.test.ts +4 -2
  34. package/src/actions/wrapper/modalBuilder.ts +6 -8
  35. package/src/clients/createIFrameFrakClient.ts +146 -25
  36. package/src/clients/transports/iframeLifecycleManager.test.ts +0 -80
  37. package/src/clients/transports/iframeLifecycleManager.ts +0 -44
  38. package/src/index.ts +5 -3
  39. package/src/types/config.ts +10 -3
  40. package/src/types/index.ts +6 -1
  41. package/src/types/lifecycle/client.ts +22 -27
  42. package/src/types/lifecycle/iframe.ts +0 -8
  43. package/src/types/resolvedConfig.ts +104 -0
  44. package/src/types/rpc/interaction.ts +4 -0
  45. package/src/types/rpc.ts +7 -5
  46. package/src/utils/backendUrl.test.ts +2 -2
  47. package/src/utils/backendUrl.ts +1 -1
  48. package/src/utils/index.ts +1 -5
  49. package/src/utils/sdkConfigStore.test.ts +405 -0
  50. package/src/utils/sdkConfigStore.ts +277 -0
  51. package/src/utils/sso.ts +3 -7
  52. package/dist/setupClient-CqTHGvVa.cjs +0 -13
  53. package/dist/setupClient-DTyvAPgh.js +0 -13
  54. package/dist/siweAuthenticate-BWmI2_TN.cjs +0 -1
  55. package/dist/siweAuthenticate-zczqxm0a.js +0 -1
  56. package/dist/trackEvent-CeLFVzZn.js +0 -1
  57. package/dist/trackEvent-Ew5r5zfI.cjs +0 -1
  58. package/src/utils/merchantId.test.ts +0 -653
  59. package/src/utils/merchantId.ts +0 -143
@@ -1,13 +1,14 @@
1
- import { A as SendTransactionModalStepType, B as PrepareSsoReturnType, E as ModalRpcMetadata, F as SiweAuthenticationParams, H as FinalActionType, I as LoginModalStepType, O as ModalRpcStepsResultType, P as SiweAuthenticateReturnType, T as DisplayModalParamsType, U as FinalModalStepType, b as DisplayEmbeddedWalletResultType, h as GetMerchantInformationReturnType, i as FrakContext, j as SendTransactionReturnType, k as ModalStepTypes, l as FrakClient, p as WalletStatusReturnType, v as SendInteractionParamsType, y as DisplayEmbeddedWalletParamsType, z as PrepareSsoParamsType } from "./openSso-B0g7-807.cjs";
1
+ import { A as SendTransactionModalStepType, B as PrepareSsoReturnType, E as ModalRpcMetadata, F as SiweAuthenticationParams, H as FinalActionType, I as LoginModalStepType, O as ModalRpcStepsResultType, P as SiweAuthenticateReturnType, T as DisplayModalParamsType, U as FinalModalStepType, b as DisplayEmbeddedWalletResultType, h as GetMerchantInformationReturnType, i as FrakContext, j as SendTransactionReturnType, k as ModalStepTypes, l as FrakClient, p as WalletStatusReturnType, v as SendInteractionParamsType, y as DisplayEmbeddedWalletParamsType, z as PrepareSsoParamsType } from "./openSso-B8v3Vtnh.js";
2
2
 
3
3
  //#region src/actions/displayEmbeddedWallet.d.ts
4
4
  /**
5
5
  * Function used to display the Frak embedded wallet popup
6
6
  * @param client - The current Frak Client
7
7
  * @param params - The parameter used to customise the embedded wallet
8
+ * @param placement - Optional placement ID to associate with this display request
8
9
  * @returns The embedded wallet display result
9
10
  */
10
- declare function displayEmbeddedWallet(client: FrakClient, params: DisplayEmbeddedWalletParamsType): Promise<DisplayEmbeddedWalletResultType>;
11
+ declare function displayEmbeddedWallet(client: FrakClient, params: DisplayEmbeddedWalletParamsType, placement?: string): Promise<DisplayEmbeddedWalletResultType>;
11
12
  //#endregion
12
13
  //#region src/actions/displayModal.d.ts
13
14
  /**
@@ -16,6 +17,7 @@ declare function displayEmbeddedWallet(client: FrakClient, params: DisplayEmbedd
16
17
  * @param args
17
18
  * @param args.steps - The different steps of the modal
18
19
  * @param args.metadata - The metadata for the modal (customization, etc)
20
+ * @param placement - Optional placement ID to associate with this modal display
19
21
  * @returns The result of each modal steps
20
22
  *
21
23
  * @description This function will display a modal to the user with the provided steps and metadata.
@@ -115,7 +117,7 @@ declare function displayEmbeddedWallet(client: FrakClient, params: DisplayEmbedd
115
117
  declare function displayModal<T extends ModalStepTypes[] = ModalStepTypes[]>(client: FrakClient, {
116
118
  steps,
117
119
  metadata
118
- }: DisplayModalParamsType<T>): Promise<ModalRpcStepsResultType<T>>;
120
+ }: DisplayModalParamsType<T>, placement?: string): Promise<ModalRpcStepsResultType<T>>;
119
121
  //#endregion
120
122
  //#region src/actions/ensureIdentity.d.ts
121
123
  /**
@@ -322,7 +324,7 @@ declare function sendInteraction(client: FrakClient, params: SendInteractionPara
322
324
  * }
323
325
  *
324
326
  * @remarks
325
- * - Merchant id is resolved in this order: explicit `args.merchantId`, `frak-merchant-id` from session storage, then `fetchMerchantId()`.
327
+ * - Merchant id is resolved in this order: explicit `args.merchantId`, then `sdkConfigStore.resolveMerchantId()` (config store sessionStorage → backend fetch).
326
328
  * - This function supports anonymous users and will use the `x-frak-client-id` header when available.
327
329
  * - At least one identity source must exist (`frak-wallet-interaction-token` or `x-frak-client-id`), otherwise the tracking request is skipped.
328
330
  * - This function will print a warning if used in a non-browser environment or if no identity / merchant id can be resolved.
@@ -380,8 +382,9 @@ type ModalStepBuilder<Steps extends ModalStepTypes[] = ModalStepTypes[]> = {
380
382
  /**
381
383
  * Display the modal
382
384
  * @param metadataOverride - Function returning optional metadata to override the current modal metadata
385
+ * @param placement - Optional placement ID to associate with this modal display
383
386
  */
384
- display: (metadataOverride?: (current?: ModalRpcMetadata) => ModalRpcMetadata | undefined) => Promise<ModalRpcStepsResultType<Steps>>;
387
+ display: (metadataOverride?: (current?: ModalRpcMetadata) => ModalRpcMetadata | undefined, placement?: string) => Promise<ModalRpcStepsResultType<Steps>>;
385
388
  };
386
389
  /**
387
390
  * Represent the output type of the modal builder
@@ -0,0 +1 @@
1
+ const e=require(`./trackEvent-B2uom25e.cjs`);let t=require(`viem`),n=require(`@frak-labs/frame-connector`),r=require(`viem/siwe`);async function i(e,t,n){return await e.request({method:`frak_displayEmbeddedWallet`,params:n?[t,e.config.metadata,n]:[t,e.config.metadata]})}async function a(e,{steps:t,metadata:n},r){return await e.request({method:`frak_displayModal`,params:r?[t,n,e.config.metadata,r]:[t,n,e.config.metadata]})}async function o(t){if(typeof window>`u`)return;let n=e.g();if(!n)return;let r=await e.n.resolveMerchantId();if(!r)return;let i=`frak-identity-ensured-${r}`;if(!window.sessionStorage.getItem(i))try{let a=e.s();(await fetch(`${a}/user/identity/ensure`,{method:`POST`,headers:{Accept:`application/json`,"Content-Type":`application/json`,"x-wallet-sdk-auth":t,"x-frak-client-id":n},body:JSON.stringify({merchantId:r})})).ok&&window.sessionStorage.setItem(i,`1`)}catch{}}async function s(e){return await e.request({method:`frak_getMerchantInformation`})}async function c(e,t){let{metadata:n,customizations:r}=e.config;return await e.request({method:`frak_prepareSso`,params:[t,n.name,r?.css]})}async function l(t,n){try{await t.request({method:`frak_sendInteraction`,params:[n,{clientId:e.g()}]})}catch{console.warn(`[Frak SDK] Failed to send interaction:`,n.type)}}function u(t,n,r){let i=typeof window<`u`?window.location.href:void 0;return e.a(n)?(e.t(t,`user_referred_started`,{properties:{referrerClientId:n.c,walletStatus:r?.key}}),l(t,{type:`arrival`,referrerClientId:n.c,referrerMerchantId:n.m,referralTimestamp:n.t,landingUrl:i}),!0):e.i(n)?(e.t(t,`user_referred_started`,{properties:{referrer:n.r,walletStatus:r?.key}}),l(t,{type:`arrival`,referrerWallet:n.r,landingUrl:i}),!0):!1}function d(t){let n=e.g();return n?{v:2,c:n,m:t,t:Math.floor(Date.now()/1e3)}:null}function f(n,r){return e.a(n)?e.g()===n.c:e.i(n)&&r?.wallet?(0,t.isAddressEqual)(n.r,r.wallet):!1}function p(t,{walletStatus:n,frakContext:r,options:i}){if(!r)return`no-referrer`;if(f(r,n))return`self-referral`;if(!u(t,r,n))return`no-referrer`;let a=e.a(r)?r.m:i?.merchantId,o=i?.alwaysAppendUrl&&a?d(a):null;return e.r.replaceUrl({url:window.location?.href,context:o}),e.t(t,`user_referred_completed`,{properties:{status:`success`}}),`success`}async function m(t,{options:n}={}){let r=e.r.parse({url:window.location.href}),i=await g(t);try{return p(t,{walletStatus:i,frakContext:r,options:n})}catch(e){console.warn(`Error processing referral`,{error:e})}}async function h(t){if(typeof window>`u`){console.warn(`[Frak] No window found, can't track purchase`);return}let n=window.sessionStorage.getItem(`frak-wallet-interaction-token`),r=e.g();if(!n&&!r){console.warn(`[Frak] No identity found, skipping purchase check`);return}let i=t.merchantId??await e.n.resolveMerchantId();if(!i){console.warn(`[Frak] No merchant id found, skipping purchase check`);return}let a={Accept:`application/json`,"Content-Type":`application/json`};n&&(a[`x-wallet-sdk-auth`]=n),r&&(a[`x-frak-client-id`]=r);let o=e.s();await fetch(`${o}/user/track/purchase`,{method:`POST`,headers:a,body:JSON.stringify({customerId:t.customerId,orderId:t.orderId,token:t.token,merchantId:i})})}function g(e,t){if(!t)return e.request({method:`frak_listenToWalletStatus`}).then(t=>(_(e,t),t));let r=new n.Deferred,i=!1;return e.listenerRequest({method:`frak_listenToWalletStatus`},n=>{_(e,n),t(n),i||=(r.resolve(n),!0)}),r.promise}function _(e,t){typeof window>`u`||(e.openPanel?.setGlobalProperties({wallet:t.wallet??null}),t.interactionToken?(window.sessionStorage.setItem(`frak-wallet-interaction-token`,t.interactionToken),o(t.interactionToken)):window.sessionStorage.removeItem(`frak-wallet-interaction-token`))}function v(e,{metadata:t,login:n}){return y(e,{steps:{login:n??{}},metadata:t})}function y(e,t){function n(n){return y(e,{...t,steps:{...t.steps,sendTransaction:n}})}function r(n){return y(e,{...t,steps:{...t.steps,final:{...n,action:{key:`reward`}}}})}function i(n,r){return y(e,{...t,steps:{...t.steps,final:{...r,action:{key:`sharing`,options:n}}}})}async function o(n,r){return n&&(t.metadata=n(t.metadata??{})),await a(e,t,r)}return{params:t,sendTx:n,reward:r,sharing:i,display:o}}async function b(e,{tx:t,metadata:n}){return(await a(e,{metadata:n,steps:{login:{},sendTransaction:{tx:t}}})).sendTransaction}async function x(e,{siwe:t,metadata:n}){let i=e.config?.domain??window.location.host,o=t?.statement??`I confirm that I want to use my Frak wallet on: ${e.config.metadata.name}`;return(await a(e,{metadata:n,steps:{login:{},siweAuthenticate:{siwe:{...t,statement:o,nonce:t?.nonce??(0,r.generateSiweNonce)(),uri:t?.uri??`https://${i}`,version:t?.version??`1`,domain:i}}}})).siweAuthenticate}Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return h}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return v}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return x}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return s}});
@@ -1,13 +1,14 @@
1
- import { A as SendTransactionModalStepType, B as PrepareSsoReturnType, E as ModalRpcMetadata, F as SiweAuthenticationParams, H as FinalActionType, I as LoginModalStepType, O as ModalRpcStepsResultType, P as SiweAuthenticateReturnType, T as DisplayModalParamsType, U as FinalModalStepType, b as DisplayEmbeddedWalletResultType, h as GetMerchantInformationReturnType, i as FrakContext, j as SendTransactionReturnType, k as ModalStepTypes, l as FrakClient, p as WalletStatusReturnType, v as SendInteractionParamsType, y as DisplayEmbeddedWalletParamsType, z as PrepareSsoParamsType } from "./openSso-CMzwvaCa.js";
1
+ import { A as SendTransactionModalStepType, B as PrepareSsoReturnType, E as ModalRpcMetadata, F as SiweAuthenticationParams, H as FinalActionType, I as LoginModalStepType, O as ModalRpcStepsResultType, P as SiweAuthenticateReturnType, T as DisplayModalParamsType, U as FinalModalStepType, b as DisplayEmbeddedWalletResultType, h as GetMerchantInformationReturnType, i as FrakContext, j as SendTransactionReturnType, k as ModalStepTypes, l as FrakClient, p as WalletStatusReturnType, v as SendInteractionParamsType, y as DisplayEmbeddedWalletParamsType, z as PrepareSsoParamsType } from "./openSso-n_B4LSuW.cjs";
2
2
 
3
3
  //#region src/actions/displayEmbeddedWallet.d.ts
4
4
  /**
5
5
  * Function used to display the Frak embedded wallet popup
6
6
  * @param client - The current Frak Client
7
7
  * @param params - The parameter used to customise the embedded wallet
8
+ * @param placement - Optional placement ID to associate with this display request
8
9
  * @returns The embedded wallet display result
9
10
  */
10
- declare function displayEmbeddedWallet(client: FrakClient, params: DisplayEmbeddedWalletParamsType): Promise<DisplayEmbeddedWalletResultType>;
11
+ declare function displayEmbeddedWallet(client: FrakClient, params: DisplayEmbeddedWalletParamsType, placement?: string): Promise<DisplayEmbeddedWalletResultType>;
11
12
  //#endregion
12
13
  //#region src/actions/displayModal.d.ts
13
14
  /**
@@ -16,6 +17,7 @@ declare function displayEmbeddedWallet(client: FrakClient, params: DisplayEmbedd
16
17
  * @param args
17
18
  * @param args.steps - The different steps of the modal
18
19
  * @param args.metadata - The metadata for the modal (customization, etc)
20
+ * @param placement - Optional placement ID to associate with this modal display
19
21
  * @returns The result of each modal steps
20
22
  *
21
23
  * @description This function will display a modal to the user with the provided steps and metadata.
@@ -115,7 +117,7 @@ declare function displayEmbeddedWallet(client: FrakClient, params: DisplayEmbedd
115
117
  declare function displayModal<T extends ModalStepTypes[] = ModalStepTypes[]>(client: FrakClient, {
116
118
  steps,
117
119
  metadata
118
- }: DisplayModalParamsType<T>): Promise<ModalRpcStepsResultType<T>>;
120
+ }: DisplayModalParamsType<T>, placement?: string): Promise<ModalRpcStepsResultType<T>>;
119
121
  //#endregion
120
122
  //#region src/actions/ensureIdentity.d.ts
121
123
  /**
@@ -322,7 +324,7 @@ declare function sendInteraction(client: FrakClient, params: SendInteractionPara
322
324
  * }
323
325
  *
324
326
  * @remarks
325
- * - Merchant id is resolved in this order: explicit `args.merchantId`, `frak-merchant-id` from session storage, then `fetchMerchantId()`.
327
+ * - Merchant id is resolved in this order: explicit `args.merchantId`, then `sdkConfigStore.resolveMerchantId()` (config store sessionStorage → backend fetch).
326
328
  * - This function supports anonymous users and will use the `x-frak-client-id` header when available.
327
329
  * - At least one identity source must exist (`frak-wallet-interaction-token` or `x-frak-client-id`), otherwise the tracking request is skipped.
328
330
  * - This function will print a warning if used in a non-browser environment or if no identity / merchant id can be resolved.
@@ -380,8 +382,9 @@ type ModalStepBuilder<Steps extends ModalStepTypes[] = ModalStepTypes[]> = {
380
382
  /**
381
383
  * Display the modal
382
384
  * @param metadataOverride - Function returning optional metadata to override the current modal metadata
385
+ * @param placement - Optional placement ID to associate with this modal display
383
386
  */
384
- display: (metadataOverride?: (current?: ModalRpcMetadata) => ModalRpcMetadata | undefined) => Promise<ModalRpcStepsResultType<Steps>>;
387
+ display: (metadataOverride?: (current?: ModalRpcMetadata) => ModalRpcMetadata | undefined, placement?: string) => Promise<ModalRpcStepsResultType<Steps>>;
385
388
  };
386
389
  /**
387
390
  * Represent the output type of the modal builder
@@ -0,0 +1 @@
1
+ import{bytesToHex as e,hexToBytes as t,isAddress as n,keccak256 as r,toHex as i}from"viem";import{jsonDecode as a,jsonEncode as o}from"@frak-labs/frame-connector";const s=`frak-client-id`;function c(){return typeof crypto<`u`&&crypto.randomUUID?crypto.randomUUID():`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e===`x`?t:t&3|8).toString(16)})}function l(){if(typeof window>`u`||!window.localStorage)return console.warn(`[Frak SDK] No Window / localStorage available to save the clientId`),c();let e=localStorage.getItem(s);return e||(e=c(),localStorage.setItem(s,e)),e}function u({domain:e}={}){return r(i((e??window.location.host).replace(`www.`,``)))}function d(e){return btoa(Array.from(e,e=>String.fromCharCode(e)).join(``)).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/,``)}function f(e){let t=e.length%4;return Uint8Array.from(atob(e.replace(/-/g,`+`).replace(/_/g,`/`).padEnd(e.length+(t===0?0:4-t),`=`)),e=>e.charCodeAt(0))}function p(e){return d(o(e))}function m(e,t,n,r,i,a){let o=p(ee({redirectUrl:t.redirectUrl,directExit:t.directExit,lang:t.lang,merchantId:n,metadata:{name:r,css:a,logoUrl:t.metadata?.logoUrl,homepageLink:t.metadata?.homepageLink},clientId:i})),s=new URL(e);return s.pathname=`/sso`,s.searchParams.set(`p`,o),s.toString()}function ee(e){return{r:e.redirectUrl,cId:e.clientId,d:e.directExit,l:e.lang,m:e.merchantId,md:{n:e.metadata?.name,css:e.metadata?.css,l:e.metadata?.logoUrl,h:e.metadata?.homepageLink}}}const h=`menubar=no,status=no,scrollbars=no,fullscreen=no,width=500, height=800`,g=`frak-sso`;async function _(e,t){let{metadata:n,customizations:r,walletUrl:i}=e.config;if(t.openInSameWindow??!!t.redirectUrl)return await e.request({method:`frak_openSso`,params:[t,n.name,r?.css]});let a=t.ssoPopupUrl??m(i??`https://wallet.frak.id`,t,u(),n.name,l(),r?.css),o=window.open(a,g,h);if(!o)throw Error(`Popup was blocked. Please allow popups for this site.`);return o.focus(),await e.request({method:`frak_openSso`,params:[t,n.name,r?.css]})??{}}const v=`https://backend.frak.id`;function y(e){return e.includes(`localhost:3000`)||e.includes(`localhost:3010`)}function b(e){return y(e)?`https://localhost:3030`:e.includes(`wallet-dev.frak.id`)||e.includes(`wallet.gcp-dev.frak.id`)?`https://backend.gcp-dev.frak.id`:v}function x(e){if(e)return b(e);if(typeof window<`u`){let e=window.FrakSetup?.client?.config?.walletUrl;if(e)return b(e)}return v}function S(e){return a(f(e))}function C(e){return`r`in e&&!(`v`in e)}function w(e){return`v`in e&&e.v===2}const T=`fCtx`;function E(e){if(e)try{return w(e)?!e.c||!e.m||!e.t?void 0:p({v:2,c:e.c,m:e.m,t:e.t}):d(t(e.r))}catch(t){console.error(`Error compressing Frak context`,{e:t,context:e})}}function D(t){if(!(!t||t.length===0))try{let r=S(t);if(r&&typeof r==`object`&&r.v===2)return r.c&&r.m&&r.t?{v:2,c:r.c,m:r.m,t:r.t}:void 0;let i=e(f(t),{size:20});if(n(i))return{r:i}}catch(e){console.error(`Error decompressing Frak context`,{e,context:t})}}function O({url:e}){if(!e)return null;let t=new URL(e).searchParams.get(T);return t?D(t):null}function k({url:e,context:t}){if(!e)return null;let n=E(t);if(!n)return null;let r=new URL(e);return r.searchParams.set(T,n),r.toString()}function A(e){let t=new URL(e);return t.searchParams.delete(T),t.toString()}function j({url:e,context:t}){if(!window.location?.href||typeof window>`u`){console.error(`No window found, can't update context`);return}let n=e??window.location.href,r;r=t===null?A(n):k({url:n,context:t}),r&&window.history.replaceState(null,``,r.toString())}const M={compress:E,decompress:D,parse:O,update:k,remove:A,replaceUrl:j},N=`__frakSdkConfig`,P=`frak-config-cache`,F=`frak-merchant-id`,I={key:P},L=typeof window<`u`;function R(){return{isResolved:!1,merchantId:``}}let z=null;function B(){if(!L)return null;try{let e=localStorage.getItem(I.key);if(!e)return null;let t=JSON.parse(e);return t.config?.isResolved?(z=t,t):null}catch{return null}}function V(){return(z??B())?.config}function H(){let e=z??B();return e?Date.now()-e.timestamp<3e4:!1}function U(e){if(!(!L||!e.isResolved))try{let t={config:e,timestamp:Date.now()};localStorage.setItem(I.key,JSON.stringify(t)),z=t}catch{}}function W(){if(L){z=null;try{localStorage.removeItem(I.key)}catch{}}}function G(){L&&(window[N]||(window[N]=V()??R()))}G();function K(){return L?window[N]??R():R()}function q(e){L&&window.dispatchEvent(new CustomEvent(`frak:config`,{detail:e}))}function J(e){return e??(L?window.location.hostname:``)}const Y=new Map,X=new Map;function Z(e,t){return`${e}:${t??``}`}async function Q(e,t,n){try{let r=x(t),i=n?`&lang=${encodeURIComponent(n)}`:``,a=await fetch(`${r}/user/merchant/resolve?domain=${encodeURIComponent(e)}${i}`);if(!a.ok){console.warn(`[Frak SDK] Merchant lookup failed for domain ${e}: ${a.status}`);return}let o=await a.json(),s=Z(e,n);if(Y.set(s,o),L)try{sessionStorage.setItem(F,o.merchantId)}catch{}return o}catch(e){console.warn(`[Frak SDK] Failed to fetch merchant config:`,e);return}}const $={getConfig:K,get isResolved(){return K().isResolved},get isCacheFresh(){return H()},setCacheScope(e,t){I.key=`${P}:${`${e}:${t??``}`}`,z=null},setConfig(e){if(L&&(window[N]=e),U(e),q(e),L&&e.merchantId)try{sessionStorage.setItem(F,e.merchantId)}catch{}},reset(){let e=V()??R();L&&(window[N]=e),q(e)},clearCache(){if(W(),Y.clear(),X.clear(),L)try{sessionStorage.removeItem(F)}catch{}},resolve(e,t,n){let r=J(e);if(!r)return Promise.resolve(void 0);let i=Z(r,n);if(Y.has(i))return Promise.resolve(Y.get(i));let a=X.get(i);if(a)return a;let o=Q(r,t,n).then(e=>(X.delete(i),e));return X.set(i,o),o},getMerchantId(){let e=K();if(e.isResolved&&e.merchantId)return e.merchantId;if(L)try{return sessionStorage.getItem(F)??void 0}catch{}},async resolveMerchantId(e,t){return $.getMerchantId()||(await $.resolve(e,t))?.merchantId}};function te(e,t,n={}){if(!e){console.debug(`[Frak] No client provided, skipping event tracking`);return}try{e.openPanel?.track(t,n)}catch(e){console.debug(`[Frak] Failed to track event:`,t,e)}}export{w as a,_ as c,m as d,p as f,l as g,u as h,C as i,h as l,d as m,$ as n,S as o,f as p,M as r,x as s,te as t,g as u};
@@ -0,0 +1 @@
1
+ let e=require(`viem`),t=require(`@frak-labs/frame-connector`);const n=`frak-client-id`;function r(){return typeof crypto<`u`&&crypto.randomUUID?crypto.randomUUID():`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e===`x`?t:t&3|8).toString(16)})}function i(){if(typeof window>`u`||!window.localStorage)return console.warn(`[Frak SDK] No Window / localStorage available to save the clientId`),r();let e=localStorage.getItem(n);return e||(e=r(),localStorage.setItem(n,e)),e}function a({domain:t}={}){return(0,e.keccak256)((0,e.toHex)((t??window.location.host).replace(`www.`,``)))}function o(e){return btoa(Array.from(e,e=>String.fromCharCode(e)).join(``)).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/,``)}function s(e){let t=e.length%4;return Uint8Array.from(atob(e.replace(/-/g,`+`).replace(/_/g,`/`).padEnd(e.length+(t===0?0:4-t),`=`)),e=>e.charCodeAt(0))}function c(e){return o((0,t.jsonEncode)(e))}function l(e,t,n,r,i,a){let o=c(u({redirectUrl:t.redirectUrl,directExit:t.directExit,lang:t.lang,merchantId:n,metadata:{name:r,css:a,logoUrl:t.metadata?.logoUrl,homepageLink:t.metadata?.homepageLink},clientId:i})),s=new URL(e);return s.pathname=`/sso`,s.searchParams.set(`p`,o),s.toString()}function u(e){return{r:e.redirectUrl,cId:e.clientId,d:e.directExit,l:e.lang,m:e.merchantId,md:{n:e.metadata?.name,css:e.metadata?.css,l:e.metadata?.logoUrl,h:e.metadata?.homepageLink}}}const d=`menubar=no,status=no,scrollbars=no,fullscreen=no,width=500, height=800`,f=`frak-sso`;async function p(e,t){let{metadata:n,customizations:r,walletUrl:o}=e.config;if(t.openInSameWindow??!!t.redirectUrl)return await e.request({method:`frak_openSso`,params:[t,n.name,r?.css]});let s=t.ssoPopupUrl??l(o??`https://wallet.frak.id`,t,a(),n.name,i(),r?.css),c=window.open(s,f,d);if(!c)throw Error(`Popup was blocked. Please allow popups for this site.`);return c.focus(),await e.request({method:`frak_openSso`,params:[t,n.name,r?.css]})??{}}const m=`https://backend.frak.id`;function h(e){return e.includes(`localhost:3000`)||e.includes(`localhost:3010`)}function g(e){return h(e)?`https://localhost:3030`:e.includes(`wallet-dev.frak.id`)||e.includes(`wallet.gcp-dev.frak.id`)?`https://backend.gcp-dev.frak.id`:m}function _(e){if(e)return g(e);if(typeof window<`u`){let e=window.FrakSetup?.client?.config?.walletUrl;if(e)return g(e)}return m}function v(e){return(0,t.jsonDecode)(s(e))}function y(e){return`r`in e&&!(`v`in e)}function b(e){return`v`in e&&e.v===2}const x=`fCtx`;function S(t){if(t)try{return b(t)?!t.c||!t.m||!t.t?void 0:c({v:2,c:t.c,m:t.m,t:t.t}):o((0,e.hexToBytes)(t.r))}catch(e){console.error(`Error compressing Frak context`,{e,context:t})}}function C(t){if(!(!t||t.length===0))try{let n=v(t);if(n&&typeof n==`object`&&n.v===2)return n.c&&n.m&&n.t?{v:2,c:n.c,m:n.m,t:n.t}:void 0;let r=(0,e.bytesToHex)(s(t),{size:20});if((0,e.isAddress)(r))return{r}}catch(e){console.error(`Error decompressing Frak context`,{e,context:t})}}function w({url:e}){if(!e)return null;let t=new URL(e).searchParams.get(x);return t?C(t):null}function T({url:e,context:t}){if(!e)return null;let n=S(t);if(!n)return null;let r=new URL(e);return r.searchParams.set(x,n),r.toString()}function E(e){let t=new URL(e);return t.searchParams.delete(x),t.toString()}function D({url:e,context:t}){if(!window.location?.href||typeof window>`u`){console.error(`No window found, can't update context`);return}let n=e??window.location.href,r;r=t===null?E(n):T({url:n,context:t}),r&&window.history.replaceState(null,``,r.toString())}const O={compress:S,decompress:C,parse:w,update:T,remove:E,replaceUrl:D},k=`__frakSdkConfig`,A=`frak-config-cache`,j=`frak-merchant-id`,M={key:A},N=typeof window<`u`;function P(){return{isResolved:!1,merchantId:``}}let F=null;function I(){if(!N)return null;try{let e=localStorage.getItem(M.key);if(!e)return null;let t=JSON.parse(e);return t.config?.isResolved?(F=t,t):null}catch{return null}}function L(){return(F??I())?.config}function R(){let e=F??I();return e?Date.now()-e.timestamp<3e4:!1}function z(e){if(!(!N||!e.isResolved))try{let t={config:e,timestamp:Date.now()};localStorage.setItem(M.key,JSON.stringify(t)),F=t}catch{}}function B(){if(N){F=null;try{localStorage.removeItem(M.key)}catch{}}}function V(){N&&(window[k]||(window[k]=L()??P()))}V();function H(){return N?window[k]??P():P()}function U(e){N&&window.dispatchEvent(new CustomEvent(`frak:config`,{detail:e}))}function W(e){return e??(N?window.location.hostname:``)}const G=new Map,K=new Map;function q(e,t){return`${e}:${t??``}`}async function J(e,t,n){try{let r=_(t),i=n?`&lang=${encodeURIComponent(n)}`:``,a=await fetch(`${r}/user/merchant/resolve?domain=${encodeURIComponent(e)}${i}`);if(!a.ok){console.warn(`[Frak SDK] Merchant lookup failed for domain ${e}: ${a.status}`);return}let o=await a.json(),s=q(e,n);if(G.set(s,o),N)try{sessionStorage.setItem(j,o.merchantId)}catch{}return o}catch(e){console.warn(`[Frak SDK] Failed to fetch merchant config:`,e);return}}const Y={getConfig:H,get isResolved(){return H().isResolved},get isCacheFresh(){return R()},setCacheScope(e,t){M.key=`${A}:${`${e}:${t??``}`}`,F=null},setConfig(e){if(N&&(window[k]=e),z(e),U(e),N&&e.merchantId)try{sessionStorage.setItem(j,e.merchantId)}catch{}},reset(){let e=L()??P();N&&(window[k]=e),U(e)},clearCache(){if(B(),G.clear(),K.clear(),N)try{sessionStorage.removeItem(j)}catch{}},resolve(e,t,n){let r=W(e);if(!r)return Promise.resolve(void 0);let i=q(r,n);if(G.has(i))return Promise.resolve(G.get(i));let a=K.get(i);if(a)return a;let o=J(r,t,n).then(e=>(K.delete(i),e));return K.set(i,o),o},getMerchantId(){let e=H();if(e.isResolved&&e.merchantId)return e.merchantId;if(N)try{return sessionStorage.getItem(j)??void 0}catch{}},async resolveMerchantId(e,t){return Y.getMerchantId()||(await Y.resolve(e,t))?.merchantId}};function X(e,t,n={}){if(!e){console.debug(`[Frak] No client provided, skipping event tracking`);return}try{e.openPanel?.track(t,n)}catch(e){console.debug(`[Frak] Failed to track event:`,t,e)}}Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,`g`,{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,`h`,{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return y}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,`m`,{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return Y}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return v}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return O}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return _}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return X}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return f}});
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "url": "https://twitter.com/QNivelais"
12
12
  }
13
13
  ],
14
- "version": "0.2.1-beta.b38eef2e",
14
+ "version": "0.2.1-beta.d2556d47",
15
15
  "description": "Core SDK of the Frak wallet, low level library to interact directly with the frak ecosystem.",
16
16
  "repository": {
17
17
  "url": "https://github.com/frak-id/wallet",
@@ -91,22 +91,22 @@
91
91
  "viem": "^2.x"
92
92
  },
93
93
  "dependencies": {
94
- "@frak-labs/frame-connector": "0.2.0-beta.b38eef2e",
95
- "@openpanel/web": "^1.0.7"
94
+ "@frak-labs/frame-connector": "0.2.0-beta.d2556d47",
95
+ "@openpanel/web": "^1.2.0"
96
96
  },
97
97
  "devDependencies": {
98
98
  "@arethetypeswrong/cli": "^0.18.2",
99
99
  "@frak-labs/dev-tooling": "0.0.0",
100
100
  "@frak-labs/test-foundation": "0.1.0",
101
101
  "@rolldown/plugin-node-polyfills": "^1.0.3",
102
- "@types/jsdom": "^27.0.0",
102
+ "@types/jsdom": "^28.0.0",
103
103
  "@types/node": "^24.10.13",
104
- "@vitest/coverage-v8": "^4.0.18",
105
- "@vitest/ui": "^4.0.18",
106
- "jsdom": "^28.0.0",
104
+ "@vitest/coverage-v8": "^4.1.0",
105
+ "@vitest/ui": "^4.1.0",
106
+ "jsdom": "^29.0.0",
107
107
  "tsdown": "^0.20.3",
108
108
  "typescript": "^5.9.3",
109
109
  "viem": "^2.39.0",
110
- "vitest": "^4.0.18"
110
+ "vitest": "^4.1.0"
111
111
  }
112
112
  }
@@ -8,14 +8,18 @@ import type {
8
8
  * Function used to display the Frak embedded wallet popup
9
9
  * @param client - The current Frak Client
10
10
  * @param params - The parameter used to customise the embedded wallet
11
+ * @param placement - Optional placement ID to associate with this display request
11
12
  * @returns The embedded wallet display result
12
13
  */
13
14
  export async function displayEmbeddedWallet(
14
15
  client: FrakClient,
15
- params: DisplayEmbeddedWalletParamsType
16
+ params: DisplayEmbeddedWalletParamsType,
17
+ placement?: string
16
18
  ): Promise<DisplayEmbeddedWalletResultType> {
17
19
  return await client.request({
18
20
  method: "frak_displayEmbeddedWallet",
19
- params: [params, client.config.metadata],
21
+ params: placement
22
+ ? [params, client.config.metadata, placement]
23
+ : [params, client.config.metadata],
20
24
  });
21
25
  }
@@ -11,6 +11,7 @@ import type {
11
11
  * @param args
12
12
  * @param args.steps - The different steps of the modal
13
13
  * @param args.metadata - The metadata for the modal (customization, etc)
14
+ * @param placement - Optional placement ID to associate with this modal display
14
15
  * @returns The result of each modal steps
15
16
  *
16
17
  * @description This function will display a modal to the user with the provided steps and metadata.
@@ -111,10 +112,13 @@ export async function displayModal<
111
112
  T extends ModalStepTypes[] = ModalStepTypes[],
112
113
  >(
113
114
  client: FrakClient,
114
- { steps, metadata }: DisplayModalParamsType<T>
115
+ { steps, metadata }: DisplayModalParamsType<T>,
116
+ placement?: string
115
117
  ): Promise<ModalRpcStepsResultType<T>> {
116
118
  return (await client.request({
117
119
  method: "frak_displayModal",
118
- params: [steps, metadata, client.config.metadata],
120
+ params: placement
121
+ ? [steps, metadata, client.config.metadata, placement]
122
+ : [steps, metadata, client.config.metadata],
119
123
  })) as ModalRpcStepsResultType<T>;
120
124
  }
@@ -1,6 +1,6 @@
1
1
  import { getBackendUrl } from "../utils/backendUrl";
2
2
  import { getClientId } from "../utils/clientId";
3
- import { fetchMerchantId } from "../utils/merchantId";
3
+ import { sdkConfigStore } from "../utils/sdkConfigStore";
4
4
 
5
5
  const ENSURE_STORAGE_PREFIX = "frak-identity-ensured-";
6
6
 
@@ -36,7 +36,7 @@ export async function ensureIdentity(interactionToken: string): Promise<void> {
36
36
  return;
37
37
  }
38
38
 
39
- const merchantId = await fetchMerchantId();
39
+ const merchantId = await sdkConfigStore.resolveMerchantId();
40
40
  if (!merchantId) {
41
41
  return;
42
42
  }
@@ -11,12 +11,14 @@ vi.mock("../utils/clientId", () => ({
11
11
  getClientId: vi.fn().mockReturnValue("test-client-id"),
12
12
  }));
13
13
 
14
- vi.mock("../utils/merchantId", () => ({
15
- fetchMerchantId: vi.fn().mockResolvedValue(undefined),
14
+ vi.mock("../utils/sdkConfigStore", () => ({
15
+ sdkConfigStore: {
16
+ resolveMerchantId: vi.fn().mockResolvedValue(undefined),
17
+ },
16
18
  }));
17
19
 
18
20
  import { getClientId } from "../utils/clientId";
19
- import { fetchMerchantId } from "../utils/merchantId";
21
+ import { sdkConfigStore } from "../utils/sdkConfigStore";
20
22
  import { trackPurchaseStatus } from "./trackPurchaseStatus";
21
23
 
22
24
  describe.sequential("trackPurchaseStatus", () => {
@@ -100,7 +102,9 @@ describe.sequential("trackPurchaseStatus", () => {
100
102
  });
101
103
 
102
104
  vi.mocked(getClientId).mockReturnValue("test-client-id");
103
- vi.mocked(fetchMerchantId).mockResolvedValue(undefined);
105
+ vi.mocked(sdkConfigStore.resolveMerchantId).mockResolvedValue(
106
+ undefined
107
+ );
104
108
 
105
109
  fetchSpy = vi.fn().mockResolvedValue({
106
110
  ok: true,
@@ -228,12 +232,15 @@ describe.sequential("trackPurchaseStatus", () => {
228
232
  test("should resolve merchantId from explicit param first", async () => {
229
233
  setupStorage({
230
234
  interactionToken: "token-123",
231
- merchantId: "session-merchant-id",
235
+ merchantId: null,
232
236
  clientId: "test-client-id",
233
237
  });
234
- vi.mocked(fetchMerchantId).mockResolvedValue("fetched-merchant-id");
235
- const merchantLookupCallsBefore =
236
- vi.mocked(fetchMerchantId).mock.calls.length;
238
+ vi.mocked(sdkConfigStore.resolveMerchantId).mockResolvedValue(
239
+ "fetched-merchant-id"
240
+ );
241
+ const merchantLookupCallsBefore = vi.mocked(
242
+ sdkConfigStore.resolveMerchantId
243
+ ).mock.calls.length;
237
244
 
238
245
  await trackPurchaseStatus({
239
246
  customerId: "cust-1",
@@ -253,19 +260,20 @@ describe.sequential("trackPurchaseStatus", () => {
253
260
  merchantId: "explicit-merchant-id",
254
261
  })
255
262
  );
256
- expect(vi.mocked(fetchMerchantId).mock.calls.length).toBe(
257
- merchantLookupCallsBefore
258
- );
263
+ expect(
264
+ vi.mocked(sdkConfigStore.resolveMerchantId).mock.calls.length
265
+ ).toBe(merchantLookupCallsBefore);
259
266
  });
260
267
 
261
268
  test("should fall back to sessionStorage for merchantId", async () => {
269
+ vi.mocked(sdkConfigStore.resolveMerchantId).mockResolvedValue(
270
+ "session-merchant-id"
271
+ );
262
272
  setupStorage({
263
273
  interactionToken: "token-123",
264
- merchantId: "session-merchant-id",
274
+ merchantId: null,
265
275
  clientId: "test-client-id",
266
276
  });
267
- const merchantLookupCallsBefore =
268
- vi.mocked(fetchMerchantId).mock.calls.length;
269
277
 
270
278
  await trackPurchaseStatus({
271
279
  customerId: "cust-1",
@@ -284,18 +292,20 @@ describe.sequential("trackPurchaseStatus", () => {
284
292
  merchantId: "session-merchant-id",
285
293
  })
286
294
  );
287
- expect(vi.mocked(fetchMerchantId).mock.calls.length).toBe(
288
- merchantLookupCallsBefore
289
- );
295
+ expect(
296
+ vi.mocked(sdkConfigStore.resolveMerchantId)
297
+ ).toHaveBeenCalled();
290
298
  });
291
299
 
292
- test("should fall back to fetchMerchantId when no explicit or sessionStorage", async () => {
300
+ test("should fall back to resolveMerchantId when no explicit merchantId", async () => {
293
301
  setupStorage({
294
302
  interactionToken: "token-123",
295
303
  merchantId: null,
296
304
  clientId: "test-client-id",
297
305
  });
298
- vi.mocked(fetchMerchantId).mockResolvedValue("fetched-merchant-id");
306
+ vi.mocked(sdkConfigStore.resolveMerchantId).mockResolvedValue(
307
+ "fetched-merchant-id"
308
+ );
299
309
 
300
310
  await trackPurchaseStatus({
301
311
  customerId: "cust-1",
@@ -322,7 +332,9 @@ describe.sequential("trackPurchaseStatus", () => {
322
332
  merchantId: null,
323
333
  clientId: "test-client-id",
324
334
  });
325
- vi.mocked(fetchMerchantId).mockResolvedValue(undefined);
335
+ vi.mocked(sdkConfigStore.resolveMerchantId).mockResolvedValue(
336
+ undefined
337
+ );
326
338
  const callCountBefore = getTrackingRequests().length;
327
339
 
328
340
  await trackPurchaseStatus({
@@ -1,6 +1,6 @@
1
1
  import { getBackendUrl } from "../utils/backendUrl";
2
2
  import { getClientId } from "../utils/clientId";
3
- import { fetchMerchantId } from "../utils/merchantId";
3
+ import { sdkConfigStore } from "../utils/sdkConfigStore";
4
4
 
5
5
  /**
6
6
  * Function used to track the status of a purchase
@@ -26,7 +26,7 @@ import { fetchMerchantId } from "../utils/merchantId";
26
26
  * }
27
27
  *
28
28
  * @remarks
29
- * - Merchant id is resolved in this order: explicit `args.merchantId`, `frak-merchant-id` from session storage, then `fetchMerchantId()`.
29
+ * - Merchant id is resolved in this order: explicit `args.merchantId`, then `sdkConfigStore.resolveMerchantId()` (config store sessionStorage → backend fetch).
30
30
  * - This function supports anonymous users and will use the `x-frak-client-id` header when available.
31
31
  * - At least one identity source must exist (`frak-wallet-interaction-token` or `x-frak-client-id`), otherwise the tracking request is skipped.
32
32
  * - This function will print a warning if used in a non-browser environment or if no identity / merchant id can be resolved.
@@ -52,10 +52,8 @@ export async function trackPurchaseStatus(args: {
52
52
  return;
53
53
  }
54
54
 
55
- const merchantIdFromStorage =
56
- window.sessionStorage.getItem("frak-merchant-id");
57
55
  const merchantId =
58
- args.merchantId ?? merchantIdFromStorage ?? (await fetchMerchantId());
56
+ args.merchantId ?? (await sdkConfigStore.resolveMerchantId());
59
57
 
60
58
  if (!merchantId) {
61
59
  console.warn("[Frak] No merchant id found, skipping purchase check");
@@ -196,7 +196,8 @@ describe("modalBuilder", () => {
196
196
 
197
197
  expect(displayModal).toHaveBeenCalledWith(
198
198
  mockClient,
199
- builder.params
199
+ builder.params,
200
+ undefined
200
201
  );
201
202
  });
202
203
 
@@ -216,7 +217,8 @@ describe("modalBuilder", () => {
216
217
  mockClient,
217
218
  expect.objectContaining({
218
219
  metadata: { header: { title: "Overridden" } },
219
- })
220
+ }),
221
+ undefined
220
222
  );
221
223
  });
222
224
 
@@ -46,11 +46,13 @@ export type ModalStepBuilder<
46
46
  /**
47
47
  * Display the modal
48
48
  * @param metadataOverride - Function returning optional metadata to override the current modal metadata
49
+ * @param placement - Optional placement ID to associate with this modal display
49
50
  */
50
51
  display: (
51
52
  metadataOverride?: (
52
53
  current?: ModalRpcMetadata
53
- ) => ModalRpcMetadata | undefined
54
+ ) => ModalRpcMetadata | undefined,
55
+ placement?: string
54
56
  ) => Promise<ModalRpcStepsResultType<Steps>>;
55
57
  };
56
58
 
@@ -177,27 +179,23 @@ function modalStepsBuilder<CurrentSteps extends ModalStepTypes[]>(
177
179
  );
178
180
  }
179
181
 
180
- // Function to display it
181
182
  async function display(
182
183
  metadataOverride?: (
183
184
  current?: ModalRpcMetadata
184
- ) => ModalRpcMetadata | undefined
185
+ ) => ModalRpcMetadata | undefined,
186
+ placement?: string
185
187
  ) {
186
- // If we have a metadata override, apply it
187
188
  if (metadataOverride) {
188
189
  params.metadata = metadataOverride(params.metadata ?? {});
189
190
  }
190
- return await displayModal(client, params);
191
+ return await displayModal(client, params, placement);
191
192
  }
192
193
 
193
194
  return {
194
- // Access current modal params
195
195
  params,
196
- // Function to add new steps
197
196
  sendTx,
198
197
  reward,
199
198
  sharing,
200
- // Display the modal
201
199
  display,
202
200
  };
203
201
  }