feat: multi-buyer identity support in AI Credits widget - #136
Conversation
Co-authored-by: blueogin <43612769+blueogin@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR expands the AI Credits widget runtime/session model from a single deterministic buyer identity to a multi-buyer identity system per payer wallet, adding UI affordances to create/import/watch buyers, switching active buyer context, and filtering history by buyer.
Changes:
- Introduces
BuyerRecord[]+activeBuyerAddressinto runtime state/session (with legacy session migration) and adds new adapter actions for buyer lifecycle (create/select/import/watch/deep-link). - Updates Manage + History UI to support buyer selection and buyer-based history filtering.
- Adds Storybook QA fixtures and Playwright smoke coverage for multi-buyer flows.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/widgets/ai-credits-widget/states.spec.ts | Adds Playwright smoke tests + screenshots for multi-buyer UI states. |
| packages/ai-credits-widget/src/widgetRuntimeContract.ts | Extends adapter state/actions for multi-buyer support and re-exports BuyerRecord. |
| packages/ai-credits-widget/src/useAiCreditsHistory.ts | Adds buyer filter state/action and client-side filtering by entry.buyerAddress. |
| packages/ai-credits-widget/src/payerSession.ts | Replaces legacy single-buyer session fields with buyers[] + activeBuyerAddress and migration logic. |
| packages/ai-credits-widget/src/index.ts | Exports the new buyer-related types for consumers. |
| packages/ai-credits-widget/src/components/manage/BuyerOperatorCard.tsx | Adds buyer selector UI and import/watch panel; disables signing for address-only buyers. |
| packages/ai-credits-widget/src/components/history/HistoryTab.tsx | Adds buyer filter dropdown fed by known buyers. |
| packages/ai-credits-widget/src/buyerKeyDerivation.ts | Makes derivation message index-aware while preserving index 0 legacy message. |
| packages/ai-credits-widget/src/AiCreditsWidget.tsx | Wires knownBuyers to History UI and defaults history filter to active buyer. |
| packages/ai-credits-widget/src/adapter.ts | Implements multi-buyer session wiring + new actions (create/select/import/watch/deep-link) and URL param parsing. |
| examples/storybook/src/stories/helpers/aiCreditsWidgetStories.tsx | Adds multi-buyer QA fixture stories. |
| examples/storybook/src/stories/ai-credits-widget/AiCreditsWidgetQA.stories.tsx | Exposes new QA fixtures in Storybook. |
Suppressed comments (3)
packages/ai-credits-widget/src/adapter.ts:894
- Deep-link signature validation currently accepts any non-empty hex string (
/^0x[0-9a-fA-F]{2,}$/), so malformed/too-short signatures will be treated as “valid”. At minimum, require a 64- or 65-byte signature (EIP-2098 compact or standard) before accepting URL params.
if (!/^0x[0-9a-fA-F]{2,}$/.test(trimmedSignature)) {
setState((prev) =>
withDerivedStatus(prev, { error: 'Deep-link buyer signature is invalid' }, true),
)
return
packages/ai-credits-widget/src/adapter.ts:1457
- Deep-link parsing runs before a payer wallet is connected. When
buyerAddress/buyerSignatureare present butaddressis still null,handleApplyDeepLinkBuyersets an error (“Connect your wallet…”) on mount. Since the effect re-runs after connect anyway, defer applying deep-link params untiladdressis available to avoid a spurious error flash.
useEffect(() => {
if (typeof window === 'undefined') return
const params = new URLSearchParams(window.location.search)
const urlBuyerAddress = params.get('buyerAddress')
const urlBuyerSignature = params.get('buyerSignature')
packages/ai-credits-widget/src/components/history/HistoryTab.tsx:524
- Options in
BuyerFilterSelecthaverole="option"but don’t indicate selection viaaria-selected, which is part of the listbox pattern. This makes it harder for screen readers to announce which buyer filter is currently selected.
tag="button"
role="option"
paddingHorizontal="$3"
paddingVertical="$2"
cursor="pointer"
…ts widget - Added support for deep link parameters, including buyer address and operator signature, to facilitate buyer registration via NCDI deep links. - Introduced validation functions for buyer address and operator signature. - Enhanced state management to handle operator consent and session updates based on deep link data. - Updated relevant components and types to accommodate new deep link functionality, ensuring a seamless user experience when applying deep links.
- Introduced new functions for managing deep link parameters, including storage and retrieval from local storage. - Updated error handling to provide clearer messages when deep link parameters are invalid or missing. - Improved state management to reflect deep link status and errors in the UI, ensuring a better user experience. - Added a fallback mechanism for deep link processing, enhancing robustness in various scenarios.
|
@blueogin is mock delay set to 0 in non storybook env? it should. it shouldnt be in production |
sirpy
left a comment
There was a problem hiding this comment.
i dont understand the latest changes.
buyers is very simple
user add buyer via private key, or generate one based on signature or add it via url params.
when user add buyer it is saved on localstorage.
this is the list of user buyers.
that's it. simple.
…and stories - Introduced derivedBuyerAddress to the AiCreditsWidgetAdapterState for improved buyer address handling. - Updated relevant components and stories to utilize derivedBuyerAddress, enhancing the management of buyer selections. - Added utility function to determine if the selected buyer is derived, improving the logic in purchase flow handling.
- Introduced `operatorConsentPending` to the `AiCreditsWidgetAdapterState` for tracking the consent submission process. - Updated relevant components and hooks to handle the new state, improving user feedback during the operator consent flow. - Enhanced UI elements to reflect the pending state, ensuring a smoother user experience during consent submission. - Adjusted Storybook stories to demonstrate the new consent pending behavior.
…ent_failed' - Modified the condition in getPayDisabledMessage to allow for 'payment_failed' status, enabling users to retry payments. - Updated AmountPicker logic to reflect the new payment status handling, improving user experience during payment processes.
… key handling - Removed activeBuyerAddress from state management, replacing it with buyerPubKey for improved clarity. - Introduced rememberPrivateKeysOnDevice flag to manage private key storage preferences. - Updated various components and hooks to utilize the new state structure, enhancing user experience during buyer selection and key management. - Refactored Storybook stories to reflect changes in buyer address handling and private key options.
…queries - Eliminated buyerAddress parameter from the credit history API request and related types, streamlining the query structure. - Updated the useAiCreditsHistory hook to reflect the removal of buyerAddress, enhancing clarity in filtering logic. - Adjusted mocked backend client to remove buyerAddress filtering, ensuring consistency across implementations.
…nd components - Eliminated rememberPrivateKeysOnDevice from state management and related components, streamlining the buyer management process. - Updated various hooks and components to reflect the removal of private key handling, enhancing clarity and reducing complexity. - Adjusted Storybook stories to align with the new state structure, ensuring consistency across the application.
|
MOCK_DELAY_MS is only in the Storybook/QA mock client (@goodwidget/ai-credits-widget/mocked). Production uses the real backendClient and never hits that delay. |
…g in-memory storage - Eliminated MEMORY_SESSIONS map to simplify session handling. - Updated readPayerSession function to directly access local storage, improving efficiency and reducing complexity. - Adjusted persistSession function to remove unnecessary session caching logic, enhancing clarity in session persistence.
A payer wallet was limited to a single deterministic buyer identity. This adds full multi-buyer identity management: create additional derived buyers, import via private key, register address-only (view/consent) buyers, switch between them, and deep-link assign a partner buyer via URL params.
Runtime contract & session model
BuyerRecordtype withtype: 'derived' | 'imported' | 'address-only',derivationIndex, andlabelpayerSessionnow storesbuyers: BuyerRecord[]+activeBuyerAddress; old single-buyer sessions auto-migrate on first readbuyersandactiveBuyerAddress;buyerPubKey/buyerPrvKeyremain as computed fields from the active buyerDerivation
buildBuyerKeyMessage(address, index)— index0preserves the exact legacy message for backward compatibility; index> 0appends(buyer N)New adapter actions
createBuyerselectBuyer(address)importBuyerFromPrivateKey(key)0x…64hex, derives address, registers asimportedselectBuyerByAddress(address)applyDeepLinkBuyer(address, sig)?buyerAddress=&buyerSignature=URL params on mount, registers partner buyerUI
BuyerOperatorCard: buyer selector list (visible when >1 buyer), "New Buyer" button, collapsible "Import Key" / "Watch Address" panel; address-only buyers show a "view only" badge and Sign Consent is disabledHistoryTab:BuyerFilterSelectdropdown fed via newknownBuyersprop; defaults to active buyer on history tab openuseAiCreditsHistory:buyerAddressFilterstate +setBuyerAddressFilteraction; client-side filtering onentry.buyerAddressStories & tests
Three new QA fixtures (
MultiBuyerManage,AddressOnlyBuyer,MultiBuyerHistory) and four Playwright smoke tests covering the new flows.