From a8d00eef8ec7408c4eb8ee3ee9ae9ccb9c4cfa80 Mon Sep 17 00:00:00 2001 From: Will Cameron Date: Tue, 17 Dec 2024 22:31:46 -0800 Subject: [PATCH] feat: add basic analytics --- index.html | 8 ++ package.json | 2 + pnpm-lock.yaml | 123 ++++++++++++++++++ src/app.tsx | 4 + .../auth-provider/auth-provider.tsx | 15 +++ src/env.d.ts | 3 - .../components/bridge-to-sbtc-card.tsx | 2 +- .../components/enroll-for-sbtc-rewards.tsx | 2 + .../choose-stacking-method/hooks/index.ts | 3 + .../utils-delegate-stx.ts | 3 +- .../stacking/start-pooled-stacking/utils.ts | 21 ++- src/utils/analytics.ts | 36 +++++ 12 files changed, 215 insertions(+), 7 deletions(-) create mode 100644 src/utils/analytics.ts diff --git a/index.html b/index.html index 981d63b1..4ebf9429 100644 --- a/index.html +++ b/index.html @@ -19,6 +19,14 @@ Leather - Earn +
diff --git a/package.json b/package.json index 24a7ab69..ce085438 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,13 @@ "dependencies": { "@emotion/react": "11.11.1", "@emotion/styled": "11.11.0", + "@leather.io/analytics": "^3.0.3", "@leather.io/tokens": "0.12.1", "@leather.io/ui": "1.37.0", "@noble/hashes": "1.3.2", "@reduxjs/toolkit": "1.9.7", "@scure/base": "1.1.3", + "@segment/analytics-next": "^1.76.0", "@stacks/auth": "7.0.2", "@stacks/blockchain-api-client": "7.3.2", "@stacks/common": "7.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a996ad96..cc5afdb5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,6 +19,9 @@ importers: '@emotion/styled': specifier: 11.11.0 version: 11.11.0(@emotion/react@11.11.1(@types/react@18.2.13)(react@18.3.1))(@types/react@18.2.13)(react@18.3.1) + '@leather.io/analytics': + specifier: ^3.0.3 + version: 3.0.3 '@leather.io/tokens': specifier: 0.12.1 version: 0.12.1 @@ -34,6 +37,9 @@ importers: '@scure/base': specifier: 1.1.3 version: 1.1.3 + '@segment/analytics-next': + specifier: ^1.76.0 + version: 1.76.0 '@stacks/auth': specifier: 7.0.2 version: 7.0.2 @@ -1793,6 +1799,9 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@leather.io/analytics@3.0.3': + resolution: {integrity: sha512-ZGcycLWi4h2nQmANx0ufy2Ah84hSllT206s8cT6zFQZUilTLtQS68L3IBIbp0GTnczKLR5Z8H2evvEI3UbEC0w==} + '@leather.io/constants@0.13.5': resolution: {integrity: sha512-FOh/F/g8WepB8HfoTXsMB/BYcm/F6INPEpyEZc3ljzaN0mLwVLO1kwgMTFU9Pq7tQlITvyWiyGHcB7OYovLoUQ==} @@ -1820,6 +1829,14 @@ packages: '@leather.io/utils@0.20.0': resolution: {integrity: sha512-Ot0oOYMku4oy3218W3Tt0ip0xjMyegOxFONqOyt/WSZe9xzTiXXUq0u3D8jwa851ZEOSCB7TgOO5RMzWK0lkLg==} + '@lukeed/csprng@1.1.0': + resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} + engines: {node: '>=8'} + + '@lukeed/uuid@2.0.1': + resolution: {integrity: sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w==} + engines: {node: '>=8'} + '@noble/hashes@1.1.5': resolution: {integrity: sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==} @@ -3212,6 +3229,27 @@ packages: '@scure/bip39@1.1.0': resolution: {integrity: sha512-pwrPOS16VeTKg98dYXQyIjJEcWfz7/1YJIwxUEPFfQPtc86Ym/1sVgQ2RLoD43AazMk2l/unK4ITySSpW2+82w==} + '@segment/analytics-core@1.8.0': + resolution: {integrity: sha512-6CrccsYRY33I3mONN2ZW8SdBpbLtu1Ict3xR+n0FemYF5RB/jG7pW6jOvDXULR8kuYMzMmGOP4HvlyUmf3qLpg==} + + '@segment/analytics-generic-utils@1.2.0': + resolution: {integrity: sha512-DfnW6mW3YQOLlDQQdR89k4EqfHb0g/3XvBXkovH1FstUN93eL1kfW9CsDcVQyH3bAC5ZsFyjA/o/1Q2j0QeoWw==} + + '@segment/analytics-next@1.76.0': + resolution: {integrity: sha512-4n4vMvX0+bfypFWuu/UJNenT/Gv2+04SsjvnQL1eBd1hngKBV56EkCW+PCJyFRQQ7BnzHgWF4mY+vOPkdoke3A==} + + '@segment/analytics.js-video-plugins@0.2.1': + resolution: {integrity: sha512-lZwCyEXT4aaHBLNK433okEKdxGAuyrVmop4BpQqQSJuRz0DglPZgd9B/XjiiWs1UyOankg2aNYMN3VcS8t4eSQ==} + + '@segment/facade@3.4.10': + resolution: {integrity: sha512-xVQBbB/lNvk/u8+ey0kC/+g8pT3l0gCT8O2y9Z+StMMn3KAFAQ9w8xfgef67tJybktOKKU7pQGRPolRM1i1pdA==} + + '@segment/isodate-traverse@1.1.1': + resolution: {integrity: sha512-+G6e1SgAUkcq0EDMi+SRLfT48TNlLPF3QnSgFGVs0V9F3o3fq/woQ2rHFlW20W0yy5NnCUH0QGU3Am2rZy/E3w==} + + '@segment/isodate@1.0.3': + resolution: {integrity: sha512-BtanDuvJqnACFkeeYje7pWULVv8RgZaqKHWwGFnL/g/TH/CcZjkIVTfGDp/MAxmilYHUkrX70SqwnYSTNEaN7A==} + '@segment/loosely-validate-event@2.0.0': resolution: {integrity: sha512-ZMCSfztDBqwotkl848ODgVcAmN4OItEWDCkshcKz0/W6gGSQayuuCtWV/MlodFivAZD793d6UgANd6wCXUfrIw==} @@ -4620,6 +4658,10 @@ packages: resolution: {integrity: sha512-JhcR/+KIjkkjiU8yEpaB/USlzVi3i5whwOjpIRNGi9svKEXZSe+Qp6IWAjFjv+2GViAoDRCUv/QLNziQxsLqDg==} engines: {node: '>=12'} + dset@3.1.4: + resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} + engines: {node: '>=4'} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -5685,6 +5727,10 @@ packages: join-component@1.1.0: resolution: {integrity: sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==} + js-cookie@3.0.1: + resolution: {integrity: sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==} + engines: {node: '>=12'} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -6299,6 +6345,9 @@ packages: nested-error-stacks@2.0.1: resolution: {integrity: sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==} + new-date@1.0.3: + resolution: {integrity: sha512-0fsVvQPbo2I18DT2zVHpezmeeNYV2JaJSrseiHLc17GNOxJzUdx5mvSigPu8LtIfZSij5i1wXnXFspEs2CD6hA==} + nice-try@1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} @@ -6389,6 +6438,9 @@ packages: resolution: {integrity: sha512-zYDMnnNrFi/1Tqh0vo3PE4p97Tpl9/4MP2k2ECvkbLOZzQuAYZJLTUYVLZb7hJhbhjT+JJxAwBGS8iu5hCSd1w==} engines: {node: '>=18'} + obj-case@0.2.1: + resolution: {integrity: sha512-PquYBBTy+Y6Ob/O2574XHhDtHJlV1cJHMCgW+rDRc9J5hhmRelJB3k5dTK/3cVmFVtzvAKuENeuLpoyTzMzkOg==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -7831,6 +7883,12 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + unfetch@3.1.2: + resolution: {integrity: sha512-L0qrK7ZeAudGiKYw6nzFjnJ2D5WHblUBwmHIqtPS6oKUd+Hcpk7/hKsSmcHsTlpd1TbTNsiRBUKRq3bHLNIqIw==} + + unfetch@4.2.0: + resolution: {integrity: sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==} + unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} engines: {node: '>=4'} @@ -10186,6 +10244,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@leather.io/analytics@3.0.3': {} + '@leather.io/constants@0.13.5': dependencies: '@leather.io/models': 0.22.0 @@ -10289,6 +10349,12 @@ snapshots: '@leather.io/rpc': 2.1.20 bignumber.js: 9.1.2 + '@lukeed/csprng@1.1.0': {} + + '@lukeed/uuid@2.0.1': + dependencies: + '@lukeed/csprng': 1.1.0 + '@noble/hashes@1.1.5': {} '@noble/hashes@1.3.2': {} @@ -12683,6 +12749,49 @@ snapshots: '@noble/hashes': 1.1.5 '@scure/base': 1.1.3 + '@segment/analytics-core@1.8.0': + dependencies: + '@lukeed/uuid': 2.0.1 + '@segment/analytics-generic-utils': 1.2.0 + dset: 3.1.4 + tslib: 2.8.1 + + '@segment/analytics-generic-utils@1.2.0': + dependencies: + tslib: 2.8.1 + + '@segment/analytics-next@1.76.0': + dependencies: + '@lukeed/uuid': 2.0.1 + '@segment/analytics-core': 1.8.0 + '@segment/analytics-generic-utils': 1.2.0 + '@segment/analytics.js-video-plugins': 0.2.1 + '@segment/facade': 3.4.10 + dset: 3.1.4 + js-cookie: 3.0.1 + node-fetch: 2.7.0 + tslib: 2.8.1 + unfetch: 4.2.0 + transitivePeerDependencies: + - encoding + + '@segment/analytics.js-video-plugins@0.2.1': + dependencies: + unfetch: 3.1.2 + + '@segment/facade@3.4.10': + dependencies: + '@segment/isodate-traverse': 1.1.1 + inherits: 2.0.4 + new-date: 1.0.3 + obj-case: 0.2.1 + + '@segment/isodate-traverse@1.1.1': + dependencies: + '@segment/isodate': 1.0.3 + + '@segment/isodate@1.0.3': {} + '@segment/loosely-validate-event@2.0.0': dependencies: component-type: 1.2.2 @@ -14411,6 +14520,8 @@ snapshots: dotenv@16.4.6: {} + dset@3.1.4: {} + eastasianwidth@0.2.0: {} ee-first@1.1.1: {} @@ -15714,6 +15825,8 @@ snapshots: join-component@1.1.0: {} + js-cookie@3.0.1: {} + js-tokens@4.0.0: {} js-yaml@3.14.1: @@ -16508,6 +16621,10 @@ snapshots: nested-error-stacks@2.0.1: {} + new-date@1.0.3: + dependencies: + '@segment/isodate': 1.0.3 + nice-try@1.0.5: {} no-case@3.0.4: @@ -16609,6 +16726,8 @@ snapshots: ob1@0.80.5: {} + obj-case@0.2.1: {} + object-assign@4.1.1: {} object-inspect@1.13.3: {} @@ -18203,6 +18322,10 @@ snapshots: undici-types@5.26.5: {} + unfetch@3.1.2: {} + + unfetch@4.2.0: {} + unicode-canonical-property-names-ecmascript@2.0.1: {} unicode-match-property-ecmascript@2.0.0: diff --git a/src/app.tsx b/src/app.tsx index 45e2f40d..2f70ce6c 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -37,6 +37,7 @@ import { StackIncrease } from './pages/stacking/stack-increase/stack-increase'; import { StartDirectStacking } from './pages/stacking/start-direct-stacking/start-direct-stacking'; import { StartLiquidStacking } from './pages/stacking/start-liquid-stacking/start-liquid-stacking'; import { StartPooledStacking } from './pages/stacking/start-pooled-stacking/start-pooled-stacking'; +import { initAnalytics } from '@utils/analytics'; const queryClient = new QueryClient({ defaultOptions: { @@ -49,6 +50,9 @@ const queryClient = new QueryClient({ function Root() { useEffect(() => void loadFonts(), []); + useEffect(() => { + void initAnalytics(); + }, []); const [searchParams] = useSearchParams(); const chain = searchParams.get('chain'); diff --git a/src/components/auth-provider/auth-provider.tsx b/src/components/auth-provider/auth-provider.tsx index 6e42cb7c..0fe7755e 100644 --- a/src/components/auth-provider/auth-provider.tsx +++ b/src/components/auth-provider/auth-provider.tsx @@ -13,6 +13,7 @@ import { validateStacksAddress as isValidStacksAddress } from '@stacks/transacti import { APP_DETAILS } from 'src/constants'; import { useStacksNetwork } from '@hooks/use-stacks-network'; +import { analytics } from '@utils/analytics'; const appConfig = new AppConfig(['store_write']); const userSession = new UserSession({ appConfig }); @@ -68,6 +69,13 @@ export function AuthProvider({ children }: Props) { return; } setIsSigningIn(true); + + const provider = window.StacksProvider?.getProductInfo?.()?.name ?? 'none'; + + analytics.untypedTrack('earn_sign_in_started', { + provider, + }); + showConnect({ userSession, appDetails: APP_DETAILS, @@ -76,10 +84,17 @@ export function AuthProvider({ children }: Props) { return provider.id === 'LeatherProvider'; }), onFinish() { + const provider = window.StacksProvider?.getProductInfo?.()?.name ?? 'none'; + analytics.untypedTrack('earn_sign_in_completed', { + provider, + }); setIsSigningIn(false); setIsSignedIn(true); }, onCancel() { + analytics.untypedTrack('earn_sign_in_cancelled', { + provider, + }); setIsSigningIn(false); }, } as AuthOptions); diff --git a/src/env.d.ts b/src/env.d.ts index ceb50c11..b6ba3f88 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -11,9 +11,6 @@ interface ImportMetaEnv { */ readonly VITE_COMMIT_SHA?: string; - /** - * TODO: document where to find the key - */ readonly VITE_SEGMENT_WRITE_KEY?: string; /** diff --git a/src/pages/choose-stacking-method/components/bridge-to-sbtc-card.tsx b/src/pages/choose-stacking-method/components/bridge-to-sbtc-card.tsx index 4f88bcf6..bed39107 100644 --- a/src/pages/choose-stacking-method/components/bridge-to-sbtc-card.tsx +++ b/src/pages/choose-stacking-method/components/bridge-to-sbtc-card.tsx @@ -36,7 +36,7 @@ export function BridgeToSBTCCard(props: ChooseStackingMethodLayoutProps) { The version of Leather installed does not support this feature. Please update - Leather (6.55+) to bridge sBTC. + Leather (6.56+) to bridge sBTC. diff --git a/src/pages/choose-stacking-method/components/enroll-for-sbtc-rewards.tsx b/src/pages/choose-stacking-method/components/enroll-for-sbtc-rewards.tsx index 418f0997..5fc466b8 100644 --- a/src/pages/choose-stacking-method/components/enroll-for-sbtc-rewards.tsx +++ b/src/pages/choose-stacking-method/components/enroll-for-sbtc-rewards.tsx @@ -10,6 +10,7 @@ import { BaseDrawer } from '@components/drawer/base-drawer'; import { openExternalLink } from '@utils/external-links'; import { ChooseStackingMethodLayoutProps } from '../types'; +import { analytics } from '@utils/analytics'; export function EnrollForSBTCRewardsCard(props: ChooseStackingMethodLayoutProps) { const [isModalOpen, setIsModalOpen] = useState(false); @@ -46,6 +47,7 @@ export function EnrollForSBTCRewardsCard(props: ChooseStackingMethodLayoutProps) onClick={() => { // TODO: Implement enrollment logic with contract call setIsModalOpen(false); + analytics.untypedTrack('sbtc_earn_enrollment_started'); openExternalLink('https://bitcoinismore.org'); }} > diff --git a/src/pages/choose-stacking-method/hooks/index.ts b/src/pages/choose-stacking-method/hooks/index.ts index f4966082..c03ff963 100644 --- a/src/pages/choose-stacking-method/hooks/index.ts +++ b/src/pages/choose-stacking-method/hooks/index.ts @@ -3,6 +3,7 @@ import { useNavigate } from '@hooks/use-navigate'; import { ChooseStackingMethodLayoutProps } from '../types'; import { hasExistingCommitment } from '../utils'; +import { analytics } from '@utils/analytics'; export const usePooledStackingButton = (props: ChooseStackingMethodLayoutProps) => { const navigate = useNavigate(); @@ -77,7 +78,9 @@ export function useLeatherSbtcBridgeButton(setUpdateModalOpen: (open: boolean) = base: 'BTC', quote: 'sBTC', }); + analytics.untypedTrack('sbtc_bridge_requested'); } catch (error) { + analytics.untypedTrack('sbtc_bridge_request_failed'); console.error('Error requesting openSwap', error); setUpdateModalOpen(true); } diff --git a/src/pages/stacking/start-pooled-stacking/utils-delegate-stx.ts b/src/pages/stacking/start-pooled-stacking/utils-delegate-stx.ts index f63ec8a1..eb891e5b 100644 --- a/src/pages/stacking/start-pooled-stacking/utils-delegate-stx.ts +++ b/src/pages/stacking/start-pooled-stacking/utils-delegate-stx.ts @@ -159,7 +159,7 @@ export function createHandleSubmit({ setIsContractCallExtensionPageOpen, navigate, }: CreateHandleSubmitArgs) { - return async function handleSubmit(values: EditingFormValues) { + return async function handleSubmit(values: EditingFormValues, onFinish?: () => void) { // TODO: handle thrown errors const [poxInfo, stackingContract] = await Promise.all([ client.getPoxInfo(), @@ -172,6 +172,7 @@ export function createHandleSubmit({ ...delegateStxOptions, onFinish() { setIsContractCallExtensionPageOpen(false); + onFinish?.(); navigate('../pooled-stacking-info'); }, onCancel() { diff --git a/src/pages/stacking/start-pooled-stacking/utils.ts b/src/pages/stacking/start-pooled-stacking/utils.ts index 508389d9..5ebace7f 100644 --- a/src/pages/stacking/start-pooled-stacking/utils.ts +++ b/src/pages/stacking/start-pooled-stacking/utils.ts @@ -7,13 +7,14 @@ import { getPoxWrapperContract, requiresAllowContractCaller, } from './utils-preset-pools'; +import { analytics } from '@utils/analytics'; interface CreateHandleSubmitArgs { hasUserConfirmedPoolWrapperContract: PoolWrapperAllowanceState; setHasUserConfirmedPoolWrapperContract: React.Dispatch< React.SetStateAction >; - handleDelegateStxSubmit: (val: EditingFormValues) => Promise; + handleDelegateStxSubmit: (val: EditingFormValues, onFinish?: () => void) => Promise; handleAllowContractCallerSubmit: ({ poxWrapperContract, onFinish, @@ -32,8 +33,23 @@ export function createHandleSubmit({ if (values.poolName && requiresAllowContractCaller(values.poolName)) { const poxWrapperContract = getPoxWrapperContract(values.poolName, network); const networkInstance = getNetworkInstance(network); + + analytics.untypedTrack('stacking_initiated', { + pool_or_protocol_name: values.poolName, + network: networkInstance, + stacking_type: 'pooled', + }); + + const trackStackCompleted = () => { + analytics.untypedTrack('stacking_completed', { + pool_or_protocol_name: values.poolName, + network: networkInstance, + stacking_type: 'pooled', + }); + }; + if (hasUserConfirmedPoolWrapperContract[networkInstance]?.[poxWrapperContract]) { - handleDelegateStxSubmit(values); + handleDelegateStxSubmit(values, trackStackCompleted); return; } else { handleAllowContractCallerSubmit({ @@ -46,6 +62,7 @@ export function createHandleSubmit({ [poxWrapperContract]: true, }, }); + trackStackCompleted(); }, }); return; diff --git a/src/utils/analytics.ts b/src/utils/analytics.ts new file mode 100644 index 00000000..e2a092cf --- /dev/null +++ b/src/utils/analytics.ts @@ -0,0 +1,36 @@ +import { AnalyticsBrowser } from '@segment/analytics-next'; + +import { configureAnalyticsClient } from '@leather.io/analytics'; + +const segmentClient = new AnalyticsBrowser(); + +export const analytics = configureAnalyticsClient({ + client: segmentClient, + defaultProperties: { + platform: 'web', + }, +}); + +export function initAnalytics() { + if (!import.meta.env.VITE_SEGMENT_WRITE_KEY) { + console.log('No Segment write key found'); + return; + } + + return analytics.client.load( + { writeKey: import.meta.env.VITE_SEGMENT_WRITE_KEY ?? '' }, + { + integrations: { + 'Segment.io': { + deliveryStrategy: { + strategy: 'batching', + config: { + size: 10, + timeout: 5000, + }, + }, + }, + }, + } + ); +}