Feat/cross app connect whitelabel#620
Conversation
New /cross-app-connect workspace serving Privy's whitelabel /cross-app/connect and /cross-app/transact pages so VeChain Kit consumers no longer route through Privy's hosted popup. Lets us add login intents, restyle the flow, decode VeChain transactions against our ABIs, and block suspicious requests. Provider tree mounts VeChainKitProvider only (it already owns Privy), and the transact page guards the EIP-712 typed data against the user's smart-account address before signing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0.2.2 hardcoded the Privy default URL for cross-app popups; from 0.3.x onward the connector fetches the provider's connect/transact URLs from the Privy backend, which means the dashboard's Custom URLs (Global wallet -> Advanced) now actually take effect. Required for the whitelabel cross-app-connect host to receive popup traffic. The kit only imports toPrivyWalletConnector from the /rainbow-kit subpath; that function's signature is backward compatible in 0.5.8 (only adds optional fields), so no kit code changes are needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the "Continue -> Privy modal" intermediate step with an inline sign-in panel that renders one button per provider and triggers each auth flow directly: - OAuth providers (Google, Apple, X, Discord) use useLoginWithOAuth's initOAuth, which redirects straight to the provider's auth page. - Email uses useLoginWithEmail's headless sendCode + loginWithCode with a Chakra PinInput for the OTP, so users never see a Privy modal at all. The ?intent=<provider> URL param auto-fires the matching flow on mount (intent=email pre-selects the email panel without sending a code). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
useLoginWithVeChain now accepts an optional intent argument:
const { login } = useLoginWithVeChain();
await login({ intent: 'google' });
When set, usePrivyCrossAppSdk resolves the registered whitelabel connect
URL via createPrivyCrossAppClient.getProviderConnectUrl() and creates a
fresh wagmi connector with overrideConnectUrl set to "<url>?intent=...".
The whitelabel host already reads the intent URL param and jumps straight
into the matching OAuth/email flow, so the user skips the provider picker.
Resolving the URL dynamically avoids hardcoding the whitelabel domain in
the kit. The no-intent path is unchanged (still uses the connector from
the wagmi config).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously the kit threw a configuration error if a consumer listed
'google', 'apple', or 'email' in loginMethods without providing a privy
prop, because those buttons triggered the kit's Privy-backed flows.
With the whitelabel cross-app-connect host these methods can now be
routed through useLoginWithVeChain({ intent }) instead:
- LoginWithGoogleButton / LoginWithAppleButton: when no privy prop,
call loginViaCrossApp({ intent }) instead of Privy's initOAuth.
- EmailLoginButton: when no privy prop, render a "Continue with Email"
button that hands the email/OTP flow off to the whitelabel host
(intent: 'email') instead of showing an inline email input that would
hit a dummy Privy app.
- Validation in VeChainKitProvider now only blocks 'github', 'passkey',
and 'more' without privy (those have no cross-app fallback yet).
Consumer dApps without their own Privy config can now ship a
"Login with Google" button that one-clicks straight to Google via
VeChain's whitelabel host.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
useLoginModalContent was the second gate hiding these buttons in the no-privy branch (line 114). Now that they fall back to the whitelabel cross-app flow, only passkey/github/more stay hidden without privy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the connect page opened with ?intent=google, Privy's initOAuth redirected to Google. After auth, Google redirected back to the same URL (still ?intent=google), the page remounted with fresh component state, and the useEffect fired initOAuth a second time -- bouncing the user back to Google forever. Persist the "already attempted" marker in sessionStorage so the flag survives the OAuth redirect. Also gate on the OAuth loading state to avoid firing while Privy is still processing the callback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Once the user is authenticated, the embedded wallet exists, and the connection request was parsed, accept the connection automatically instead of waiting for a Connect button click. The user already opted in by clicking the login button on the requester dApp; a separate Accept click was redundant. Skipped after a failure so the manual button stays available as a fallback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The hook now checks for a privy prop itself and routes
google/apple/twitter/discord through useLoginWithVeChain({ intent })
when there isn't one, instead of erroring against the dummy privy
app id. github (and any other provider with no cross-app fallback)
still throws a clear error.
Cleans up the per-button manual branching in LoginWithGoogleButton
and LoginWithAppleButton -- they just call initOAuth and let the
hook pick the right path.
This also fixes the playground's "OAuth Login Examples" section,
which calls useLoginWithOAuth directly and was hanging without
a privy config.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ated Returning users with a live Privy session got the popup snapped open-and-shut with no chance to see who was asking to connect. Now: capture whether the user was authenticated on first ready=true. - If yes (returning), don't auto-accept -- show the Connect button so they can confirm the requester. - If no (logged in via OAuth/email during this popup session), auto-accept once everything is in place. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Expand CrossAppLoginIntent and the cross-app fallback set in useLoginWithOAuth to cover every Privy-supported OAuth provider (google, apple, twitter, discord, github, spotify, instagram, tiktok, line, linkedin) plus email. Only passkey and 'more' still require a privy prop on VeChainKitProvider. Updates the validation in VeChainKitProvider and useLoginModalContent to match: github / twitter / discord / etc. now work without a consumer-supplied privy config. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-in panel
SignInPanel now renders a 2-column icon grid covering google, apple,
twitter (X), discord, github, spotify, instagram, tiktok, line, and
linkedin. Email keeps its own row underneath. Each button calls
useLoginWithOAuth.initOAuth({ provider }) directly so the user
redirects straight to that provider's OAuth page.
The ?intent=<provider> URL param auto-fires the matching flow for
any of these providers (intent=email still pre-selects the email
panel without sending a code).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Playground and homepage now render a 3-column grid of buttons for every Privy OAuth provider (google, apple, twitter (X), discord, github, spotify, instagram, tiktok, line, linkedin) instead of just google + github. Updates the "Note" copy underneath: with the new kit fallback, these buttons no longer require a Privy app configured by the consumer. Without a privy prop, the hook routes through the VeChain whitelabel cross-app host. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…n privy Earlier commits added all 10 of Privy's documented OAuth providers, but only 7 are enabled in VeChain's Privy dashboard: google, apple, twitter, discord, github, tiktok, line. Calling the others would fail at the provider with a useless error. Drop spotify / instagram / linkedin from CrossAppLoginIntent, the useLoginWithOAuth fallback set, the cross-app-connect sign-in panel, and the OAuth Login Examples in the playground and homepage. Farcaster and WhatsApp are also enabled in the dashboard but use non-OAuth flows (Farcaster SIWF, WhatsApp OTP) -- noted in code comments as TODOs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Most Privy-backed login methods (google, apple, email, twitter, etc.) no longer require a host-supplied privy config -- the kit routes them through the VeChain whitelabel cross-app host when privy is missing. Update the "Without Privy" diagram, the Method values table, and the migration notes to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the popup opened with ?intent=github but the user was already authenticated (e.g. logged in with Google in a previous popup session), the host showed the existing Google session and silently ignored the github intent. Now: detect the intent/auth mismatch and logout first so the requested provider's OAuth flow runs cleanly. Guard against the post-OAuth-redirect case via the existing sessionStorage marker -- if we already initiated an OAuth attempt for this intent, we're back from the redirect and the current session is the one the user just authenticated with; no logout needed. Also adjusts the auto-accept gate: when a user came with an explicit intent they already consented on the requester dApp, so accept regardless of initial auth state once the (now-correct) provider's session is in place. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related issues: 1. The previous "auto-accept whenever intent is set" rule removed the Connect button for returning users who came with an intent that already matches a linked account. They expected to see their account + a Connect button, not a silent close. 2. The flow logout -> wait -> oauth flashed the old session's Connect panel briefly before switching to a spinner. Restructure as a phase machine computed up-front: - loading | no_params | parse_error: existing error / spinner UIs. - switching_provider: intent set, authenticated, but user does NOT have the intent provider linked -> spinner + trigger logout. - auth_pending: intent set, not authenticated -> spinner + trigger initOAuth (sessionStorage marker guards the post-redirect reload). - show_picker: no intent (or intent='email'), not authenticated -> the all-providers SignInPanel. - show_connect: ready to accept. Auto-accepts only if the user authenticated during this popup session (initialAuth was false); otherwise renders the manual Connect button. `hasLinkedProvider(user, intent)` checks user.google / user.github / etc. to tell "matching" from "stale" sessions. Auto-OAuth trigger moved up to the parent so the spinner phases don't unmount it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the next-template-derived placeholder theme with an opinionated VeChain-branded Chakra theme: - Brand tokens (cross-app-connect/src/app/theme/brand.ts) pulled from the official guidelines at https://files.vechain.org/branding: purple #7266FF, dark purple #0C0A1F, cool gray #F0F0F5, almost white #FCFCFD; Satoshi/Inter type stack. - Semantic color tokens (page-bg, card-bg, text-strong / -muted / -subtle, btn-row-*, chip-*, brand-accent) map to light or dark values via Chakra's _light / _dark scheme. initialColorMode + useSystemColorMode follow the user's OS preference. - Two Button variants ('brand' for primary CTAs, 'row' for the stacked sign-in list) plus a Card base style with brand corner radius. - Inter is loaded from Google Fonts; Satoshi falls back to Inter for now (would need self-hosting from files.vechain.org/branding/Fonts). - VechainHeader component drops the logo (light/dark wordmark from the official zip, stored under /public/brand) + title + subtitle on every page. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Redesign the no-intent SignInPanel to mirror the Privy hosted UI
(images shared by the user):
- Stacked full-width rows instead of a 2-column icon grid.
- Recent provider surfaces on top of the list with a "Recent"
badge. Tracked in localStorage per Privy app id via
cross-app/_lib/recent.ts; written when OAuth / email is
initiated. Survives popup lifetime so the next session opens
with the same provider on top.
- Less-used providers (Apple, GitHub, TikTok, LINE) collapse under
a single "Other socials" row that expands inline. Primary tier
(Google, X, Discord) and email stay visible by default.
- Monochrome glyphs (Apple, GitHub, TikTok, X) flip with the color
mode so they stay legible in both themes.
- VechainHeader replaces the inline "Sign in to VeChain" heading,
with the requester origin shown as subtitle ("Sign in to your
VeChain wallet to grant <origin> access").
- Landing page wired through the same header for consistency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the dark-only hardcoded styles (whiteAlpha, gray.400, blue button) with semantic brand tokens so the transact page picks up light or dark mode automatically. VechainHeader replaces the inline card header. Approve button switches to the brand variant; the primaryType badge now uses the chip token. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…oggle
Chakra mutates body.className after hydration to apply the resolved
color mode ('chakra-ui-light' / 'chakra-ui-dark'). With system color
detection enabled this differs from the SSR-rendered HTML and triggers
React's hydration warning. Add suppressHydrationWarning on <body>
(same recipe Chakra docs use) -- safe because only the className is
mutated, not user-visible content.
Also drop a floating ColorModeToggle in the bottom-right corner for
debug: visible in dev by default, and toggleable in production via
NEXT_PUBLIC_SHOW_COLOR_MODE_TOGGLE=true.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two issues: - Toggle was inside VechainKitProviderWrapper which is loaded with ssr:false, so it didn't render until that wrapper hydrated and appeared missing on initial paint of /cross-app/connect. - bg=card-bg + border=card-border meant near-zero contrast on both light (white-on-white) and dark surfaces. Move it out of the VechainKit wrapper (still under ChakraProvider for theme access), use the brand purple as bg with white icon and a soft shadow so it's obvious in either mode. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Less likely to overlap form CTAs and the auto-accept spinner that sits center-bottom of the card. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
VeChain's Privy app has email disabled and prefers phone over email
for OTP-based logins. Reorder + reshape the SignInPanel:
Primary (always visible):
Google, Apple, X, Phone
Other socials (expandable):
Discord, GitHub, TikTok, Farcaster, LINE
- Drop the email row + the email/OTP form. Email is no longer a
cross-app intent.
- Add a Phone row that opens an inline SMS OTP form powered by
useLoginWithSms. Mirrors the previous email flow (phone input ->
6-digit PinInput -> verify), including the back button and the
sendCode loading/awaiting/submitting states.
- Add a Farcaster row that flips the panel to a "coming soon"
placeholder. Farcaster needs SIWF via Warpcast QR/deeplink, which
isn't wired up here yet -- left as a TODO so the row is visible
but doesn't silently fail.
- Internal view state is now a PanelView union ('picker' | 'phone'
| 'farcaster') instead of a boolean showEmail flag.
- Recent badge / Other socials expandable behave as before; the
hasLinkedProvider helper learned about user.phone and
user.farcaster.
- The intent passthrough understands 'phone' and 'farcaster' (they
flip the SignInPanel into the matching view on mount); only OAuth
intents auto-redirect.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
VeChain's Privy app has email disabled, so the whitelabel cross-app-connect host can't accept email-based logins. Roll back the earlier "email falls back to cross-app when no privy" change: - CrossAppLoginIntent: drop 'email', add 'phone' and 'farcaster' (matching the new SignInPanel options on the host). - VeChainKitProvider validation: 'email' is back in the require-Privy list alongside 'passkey' and 'more'. Listing 'email' in loginMethods without a privy prop throws again, with the same error message as before. - useLoginModalContent: showEmailLogin forced to false when there is no privy, so the connect modal stops rendering the email row for consumers that aren't running their own Privy app. - EmailLoginButton: drop the EmailLoginCrossAppButton fallback that was routing to intent='email'. The component now only handles the Privy-backed inline email + OTP flow; useLoginModalContent already hides it when privy is absent. Phone is now a first-class cross-app intent and Farcaster is reserved for the eventual SIWF integration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two theme bugs visible in dark mode: - VechainHeader picked the wordmark src via useColorMode(), which returns the SSR default before hydration. The wrong-color logo flashed for a beat after every reload. Render both wordmarks and let Chakra's _light / _dark CSS pseudo selectors swap them -- no React state, no flicker. - The Cancel button used variant="ghost", which falls back to Chakra's default gray.700 / whiteAlpha.700 text. Almost invisible on the VeChain dark-purple background. Override the ghost variant in the theme so it uses text-muted with text-strong on hover -- applies everywhere (Cancel rows, "Back" buttons, etc.) without per-button color props. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous theme leaned heavily on VeChain Purple as the primary
button color, which doesn't match what the kit actually ships. The
kit's defaults (packages/vechain-kit/src/theme/tokens.ts) are clean
monochrome with a blue accent:
Light: white modal, off-white cards (#F5F5F5), near-black text
(#2E2E2E), dark pill primary (#272A2E -> white), blue
accent (#3B82F6).
Dark: charcoal modal (#151515), translucent black cards, off-white
text (rgb(223,223,221)), white pill primary (white -> black),
lighter blue accent (#60A5FA).
Radii: 8 / 12 / 16 / 24 / pill.
Type: Satoshi heading, Inter body, 15px / 600 on login rows,
-0.005em letter spacing.
Rewrite the cross-app-connect theme around those tokens. Drop the
"brand-accent / brand-accent-hover" semantic tokens (which forced
the purple onto every CTA) in favour of "primary-btn-bg /
primary-btn-color" (monochrome) and "accent" (blue, for spinners
/ focus rings). The provider rows now match the kit's loginIn
variant: 52px tall, 16px radius, 15px/600 label, subtle border.
The primary CTA picks up the kit's vechainKitPrimary look: 60px
tall, pill-shaped, hover-by-opacity.
Brand identity stays via the VeChain wordmark + the purple "Recent"
chip; everything else aligns with the kit so the host doesn't feel
like a different app dropped into the flow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five small interaction fixes:
- Wrap the requester identity in a RequesterChip component: lock icon
for HTTPS, favicon via Google's S2 service, hostname stripped of
scheme / default ports. Replaces the bare URL-in-a-sentence pattern
that read like a dev console.
- Replace the "Other socials" chevron-row with a "+N more options"
text link below the picker stack. The previous chevron-inside-row
pattern collided two interactions; the link separates them clearly.
- Swap the purple "Recent" pill for a small green dot + tooltip
("Last used"). Same recall affordance, far less visual noise next
to the primary CTA.
- Provider row labels are now "Continue with X" instead of just "X"
-- sets up the verb so the row reads as an action.
- Ghost button variant now ships a baseline border + pill shape +
48px height so "Cancel" reads as an outlined button at rest rather
than stray text. Added a separate `link` variant for the new
"more options" affordance.
- VechainHeader takes an optional requesterUrl prop that renders the
RequesterChip under the title; the loading / picker / confirm
screens use it consistently instead of inlining the origin into
the subtitle string.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Apple, GitHub, and X are intentionally monochrome (their identity is black/white) and continue to flip with the color mode. The other providers now use their brand hex so the picker has the same chromatic feel as Privy's hosted UI: Discord -> #5865F2 (Blurple) TikTok -> #FE2C55 (signature pink) LINE -> #06C755 (LINE green) Phone -> #34C759 (iMessage-style green for the SMS row) Google keeps its own multi-color glyph (FcGoogle is already colored). Brand colors live in a small BRAND_GLYPH_COLOR map; ProviderRow consults it before falling back to the monochrome flip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The transact screen previously showed a raw EXECUTEBATCHWITHAUTHORIZATION
badge plus truncated hex blobs -- meaningless to non-crypto users and
gave every clause the same visual weight regardless of risk. Rewrite
the screen to translate calldata into human-readable actions.
Three decode tiers (cross-app-connect/src/app/cross-app/_lib/decoder.ts):
1. Native VET transfer (no calldata, value > 0)
-> "Send 1.5 VET"
2. ERC-20 transfer / approve via 4-byte selector match
-> "Send 10 B3TR" / "Allow spending up to 100 USDC" /
"Allow unlimited B3TR spending"
Tokens looked up in a static address book that mirrors the kit's
mainnet / testnet / solo configs (B3TR, VOT3, VTHO across each).
Unknown ERC-20s fall back to "tokens" with 18 decimals as a guess.
3. Anything else -> b32 lookup at https://b32.vecha.in/
-> "Run swap exact tokens for tokens on a contract" /
"Interact with a contract" when even b32 doesn't know.
Results cached in-memory so re-renders don't refetch.
UI now leads with a "This app wants to:" list of action rows: icon +
plain-language summary + recipient. Warning banners surface when:
- any clause is undecodable ("we couldn't double-check every step")
- an unlimited approve is requested
The Continue CTA reads "Continue anyway" when the unknown-clause
warning is showing, to slow the user down before signing something
that couldn't be verified. Existing safety guards (smart-account
mismatch, chain id mismatch, unsupported primaryType) still block
the Continue button outright.
Technical details (smart-account address, network, primaryType, raw
clauses with to/value/data) move behind an "Inspect details"
collapsible at the bottom -- present for power users but out of the
way for the 90% of users who shouldn't have to read calldata.
Adds @vechain/dapp-kit-react, @vechain/sdk-network, viem as direct
deps of cross-app-connect (already in the kit's transitive tree).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The previous decoder fell back to "Send X tokens" for any ERC-20 contract not in the static address book -- which meant common cases like USDT, WoV, Tessera token, custom dApp tokens all rendered without their actual ticker. The user's example 0xa9059cbb... transfer hit this path. Mirror VeWorld mobile's approach: when the address book misses, read symbol() and decimals() live from Thor and cache the result. Uses the kit's executeCallClause helper (re-exported via @vechain/vechain-kit/utils) over a viem-typed ERC-20 metadata ABI, two parallel reads per unknown token. Results memoized in a module-level Map so the same token across multiple clauses (or re-renders during the OAuth round-trip) hits cache. Lookup order is now: 1. Static address book (VET / VTHO / B3TR / VOT3 -- instant) 2. In-memory cache from a previous live lookup 3. Live Thor read of symbol() + decimals() 4. Generic "tokens" / 18 decimals fallback if Thor errors The transact page passes useThor() into decodeClause() and adds it to the decode effect's deps so the first render with a Thor client triggers the lookup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Modern wallets (Rabby, MetaMask) show a balance-change summary before
the user signs -- "USDC 1,234.50 -> 1,224.50 (-10)". Add the same to
the transact screen so non-crypto users can see what's actually
leaving their wallet before they tap Continue.
cross-app-connect/src/app/cross-app/_lib/simulate.ts aggregates
predictable deltas from the decoded clauses:
- native_transfer -> -amount VET
- token_transfer -> -amount on that token
- token_approve -> no balance prediction (allowance only)
- unknown -> marks the whole simulation as partial
For each token with a delta, it reads the user's current balance via
Thor (thor.accounts.getAccount for VET, balanceOf via the kit's
executeCallClause for ERC-20). Renders as:
VET 100.0 -> 95.0 -5.0
USDC 1,234 -> 1,224 -10
When any clause is `unknown` (couldn't be decoded), the panel says
"Other changes are possible -- this app called something we couldn't
simulate" so the preview never pretends to be exhaustive. When the
simulation finds nothing predictable, it says so explicitly.
Cross-app smart-account flows are fee-delegated, so the panel adds a
quiet "Network fees are covered for you" footnote instead of a VTHO
gas line. If decoding flagged unknowns, the footnote is suppressed --
no point promising "free" when we don't fully know what's happening.
A small Spinner-with-text placeholder ("Checking how this affects
your balance...") shows while the Thor reads are in flight, so the
UI doesn't jump when results arrive.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
🚀 Preview environment deployed!Preview URL: https://preview.vechainkit.vechain.org/featcross-app-connect-whitelabel |
Three transact-screen UX fixes:
1. Recipient address resolution. cross-app-connect/_lib/contracts.ts
walks the kit's appConfig and labels every VeChain-maintained
contract by name -- B3TR Token, VeBetter Treasury, X2Earn Rewards
Pool, Stargate, Smart Account Factory, VeChain Domains, ...
The new AddressTag component (components/AddressTag.tsx) renders:
- Verified contract -> friendly label + green check tooltip
- User's own address -> "Your account" + green check
- Anything else -> truncated hex + orange warning ("Unverified
contract -- make sure you trust it before continuing")
Wired into every place the page used to dump truncated hex: the
"To" / "Spender" line under each ActionRow, and the per-clause
"to" row inside the Inspect details panel.
2. value 0x0 / data 0x dev noise. In the Inspect details:
- value rows render as "0.5 VET" (formatUnits) instead of raw
hex, and disappear entirely when BigInt(value) === 0.
- data is hidden by default behind a per-clause "Show raw
calldata" link button. Click to expand a Collapse with the
full hex (no more arbitrary 80-char truncation).
3. Clause count visibility. The Inspect heading is now
"Clause" / "Clauses (3)" depending on count, and each row labels
itself "Clause 2 of 3 · to" so the user can tell whether more
are hidden.
Removed the leftover describeDetail() helper; replaced by the new
ActionRowDetail subcomponent that emits an AddressTag for the
recipient/spender slot.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Size Change: +15.5 kB (+0.18%) Total Size: 8.71 MB
ℹ️ View Unchanged
|
Five polish items on the transact screen:
- Friendly primaryType label. _lib/labels.ts.humanPrimaryType maps
ExecuteWithAuthorization / ExecuteBatchWithAuthorization to
"Authorized call" / "Authorized batch call". The Inspect "Type"
badge now reads that instead of SHOUTING_CAPS.
- Plain-English summary sentence. _lib/labels.ts.summarizeActions
reads the decoded clauses and writes one sentence like:
"You're about to give this app unlimited access to your tokens."
"You're sending tokens out of your wallet in 3 steps."
"You're approving 4 actions, some of which we couldn't fully
verify."
Renders as the VechainHeader subtitle in place of the bland
"This app wants to:" label. Goal: dramatically reduce the
"what's this going to do?" support load.
- Title shield icon. VechainHeader accepts an optional `titleIcon`
prop. The transact screen uses LuShieldCheck colored with the
brand accent so the title visually anchors the security framing.
- Smart-account chip with copy + balance. New AccountChip component
sits at the top of the transact card body: wallet icon, truncated
address (with a tooltip-d Copy button via useClipboard), and the
user's live VET balance fetched from Thor on mount. Mirrors the
"0x129...60ef . 1,240 VET" pattern modern wallets use.
- AddressTag now distinguishes 'contract' from 'recipient'. For
token transfers, the "to" argument is a destination wallet, not a
contract -- showing "Unverified contract" there was a bug
(flagged by user). 'recipient' mode renders the truncated address
without the warning badge; if the address happens to resolve via
appConfig it still gets a label + check. 'contract' mode
(token_approve spender, raw clause `to` in Inspect, the actual
contract the user is calling) keeps the verified/unverified
phishing defence.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match the kit's address display pattern from VeWorld:
[dan.vet avatar] dan.vet
0x3f90...Dcee
instead of bare truncated hex. Powered by the kit's existing
useVechainDomain + useGetAvatarOfAddress hooks -- which already
handle the VNS lookup (.vet domain resolution), the avatar custom
field on the resolver, and a Picasso-generated identicon fallback
when neither is set.
Updated components:
- AddressTag: when the address isn't a verified VeChain contract,
render `[avatar] [domain or truncated]`. Verified contracts keep
the label-plus-check treatment (the brand is implicit in the name,
no avatar needed). `kind='contract'` still surfaces the orange
"Unverified contract" warning; `kind='recipient'` does not.
- AccountChip: identicon / domain avatar to the left of the address
block, domain as the primary label when set (truncated hex demoted
to a small caption underneath), VET balance right-aligned. Mirrors
the wallet card layout from the VeWorld screenshot the user shared.
React Query caches results across instances, so rendering N
AddressTags pointing at the same address triggers one fetch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per user request, remove the "Your wallet will" before/after balance preview that was added in 4471de1. Specifically: - Delete cross-app/_lib/simulate.ts (the aggregate-deltas-then-fetch flow). - Remove the simulation state, the decoded-clauses -> simulate effect, the BalanceChangeSection + BalanceChangeRow components, and the Simulation type import from the transact page. The AccountChip still shows the user's live VET balance, the balanceOf-driven token decoding in decoder.ts still resolves symbol + decimals for unknown tokens, and the action list still reads in plain language -- so the user knows what's being sent, just without the "100 -> 90" preview row. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the consumer dApp doesn't pass a privy prop the kit was mounting
PrivyProvider with a dummy app id (clzdb5k0b02b9qvzjm6jpknsc) plus a
dummy client id. The PrivyProvider hits POST /api/v1/sessions on
mount and any deploy whose origin wasn't on the dummy app's allow
list got back 403 -- stalling the connect button forever (most
recently spotted on preview.vechainkit.vechain.org).
Use VECHAIN_PRIVY_APP_ID instead. That app id:
- is the same one the whitelabel cross-app-connect host serves, so
the requester and host are consistent;
- has the full kit-ecosystem allowed origins list maintained by the
VeChain team, which covers preview / staging / production
domains and localhost;
- keeps the cross-app OAuth fallback intact, because that fallback
is gated on the `privy` prop in useVeChainKitConfig() -- not on
which app id is actually mounted in PrivyProvider.
clientId drops to '' (the previous dummy client id was scoped to the
dummy app and wouldn't authenticate against VECHAIN_PRIVY_APP_ID
anyway; the PrivyProvider treats it as "web flow, no multi-env
client").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five UX upgrades on the transact screen, plus consistency fixes:
1. Risk-adaptive shield. computeRisk() classifies the batch into
safe -- everything decoded cleanly
caution -- one of (unknown clause | unlimited approve)
danger -- both, or the page is in a blocked state
The title icon swaps to match: LuShieldCheck/accent (safe),
LuShieldAlert/orange.400 (caution), LuShieldX/red.400 (danger).
VechainHeader now takes a titleIconColor prop driven by this map.
2. Specific titles instead of generic "Confirm action". titleForActions
returns "Send tokens", "Approve spending", "Interact with contract",
"Confirm N actions" (mixed batches), or "Action blocked" -- so the
user lands on the page already knowing what kind of thing they're
about to do. The plain-English summary now demotes to subtitle.
3. Risk-aware Continue copy. continueLabel: "Continue" / "Continue
anyway" / "I understand, continue". The strongest verb only fires
when both warnings stack -- otherwise it'd cry wolf.
4. Token-aware AccountChip. uniqueTokensFromDecoded() collects every
ERC-20 the batch touches; AccountChip now batches one balanceOf
per token alongside the native VET read, so the chip reads
"1,240 B3TR · 12 VET" instead of just "12 VET" when the batch is
moving B3TR.
5. Action row redesign. Drop the round LuArrowUpRight / LuShieldCheck
icon container -- the strong title carries the action already.
Each row now has a subtle left-border (2px, accent-colored on
warning-bearing rows) so risk scans at a glance without
decorative chrome.
Also: AddressTag now renders the avatar (Picasso identicon by default)
next to verified-contract labels too, not just unresolved addresses.
Verified spenders / contracts no longer feel like a different family
from "dan.vet" two rows away.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously rendered in dev automatically; now opt-in via the NEXT_PUBLIC_SHOW_COLOR_MODE_TOGGLE=true env var so it's out of the way during normal flow testing. Toggle behaviour itself unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Use the square VeChain logomark (V monogram) instead of the horizontal wordmark. The mark is more compact on the popup's narrow card width and matches what Privy's hosted UI does (logomark above title). Both light + dark variants already lived under public/brand from the original logos.zip extract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Returning-user view on the connect page used to show a flat
"Signed in as <email> / Wallet 0xabc..." block reading the embedded
EOA. Three upgrades:
1. Identity row -- avatar + .vet domain + smart-account address.
useSmartAccount(embedded.address) resolves the actual VeChain
smart-account address (what the user identifies as on-chain).
useVechainDomain + useGetAvatarOfAddress reuse the kit hooks to
surface the .vet name and avatar / Picasso identicon. Display
only -- acceptConnection still passes the embedded EOA address,
which is what the cross-app session protocol expects.
2. Linked-social badges. linkedSocials() walks user.{google,
apple, twitter, discord, github, tiktok, line, phone, farcaster}
and renders a small brand-coloured icon for each linked account.
Tooltip on each badge so the brand colour alone doesn't have to
carry the meaning.
3. Non-invasive "Use another account" link. Plain text link in the
identity row footer, calls Privy useLogout(). On flip to
!authenticated the page re-renders into the show_picker phase
where the user picks again. No modal, no confirm dialog -- the
only way to lose data here is the connect attempt itself, which
they can also just Cancel.
Also: force light mode for now. theme.tsx sets initialColorMode
'light' + useSystemColorMode false (was 'system' / true). Toggle
component is already env-gated and stays hidden.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three changes that landed together:
1. App-hub verification. scripts/fetch-app-hub.mjs bakes a snapshot
of vechain/app-hub@master into _lib/app-hub.json keyed by origin
(122 entries). _lib/app-hub.ts exposes lookupAppByUrl() so the
runtime check is O(1) string lookup.
RequesterChip uses it: listed dApps render their canonical
`name` + a green check + green chip border, "Listed in the
VeChain App Hub" tooltip. Unlisted dApps render the hostname
+ orange warning, "Not listed in the VeChain App Hub..."
tooltip.
Connect-page wiring:
- Title becomes "Connect to <App Name>" when verified, falls back
to a plain "Confirm connection" when not.
- Unverified state renders a left-accent warning Alert and the
Continue button reads "Continue anyway".
- The kit ships a `yarn workspace cross-app-connect
generate-app-hub` script to refresh the snapshot when new
dApps register.
2. IdentityRow rebalance. The previous layout put the wallet domain
/ address in the primary slot and the email in a secondary
"Signed in as" line. Flipped: email is the primary, bold
text -- it's how the user thinks about themselves -- with the
linked-social badges immediately to the right of the email.
Wallet (domain + truncated address, mono) becomes the secondary
caption.
"Use another account" is no longer inside the row; it now lives
as a small "Not you? Use another account" link _below_ the
Continue / Cancel buttons. Doesn't compete with the main CTA.
3. Force light mode actually works now. theme.config already had
initialColorMode 'light' + useSystemColorMode false, but
Chakra's ColorModeScript reads localStorage before that takes
effect -- so a user who toggled dark earlier kept seeing dark.
New ForceColorMode component mounts inside ChakraProvider and
re-applies setColorMode('light') once on hydration, overriding
the cached value. Also fixed `darkMode` prop on
VechainKitProviderWrapper which was hardcoded to true.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the AlertIcon, shrink to xs / 1.3 line-height, and trim "Only continue if you trust the site in the chip above." -> "...so only continue if you trust the site." Reads less like a stack-trace next to the friendlier identity row. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three spots where waiting on async data was rendering a generic Spinner that flashes-then-jumps. Replace with shaped skeletons that match the eventual content layout: - AccountChip: SkeletonCircle for the avatar, Skeleton bar for the address / domain block, Skeleton bar for the right-side balance list. Driven by useVechainDomain.isPending / .isPending + balances === null, so the chip morphs into final state inline instead of jumping. - IdentityRow (connect screen): same pattern -- SkeletonCircle on the avatar, Skeleton bar replacing the wallet line until the domain query resolves. Email + linked-social badges keep rendering instantly (they come from the Privy user object). - Transact ActionRow list: while decoded === null, render parsed.clauses.length placeholder rows shaped like the real ActionRow (two stacked skeleton bars behind the same left-border treatment). Replaces the centered Spinner so the card layout doesn't shift when decode resolves. Skeleton colors track the theme tokens (login-btn-hover-bg / card-border) so the pulse blends with the card surface in both light and dark. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pull the transact card in line with the cleaner shape we converged on
for the connect card so the two pages feel like one design language:
- Stack spacing: 5 -> 4 to match the connect card's rhythm.
- Warning / error alerts: drop AlertIcon, use the left-accent variant
with fontSize=xs / lineHeight=1.3 (same pattern that landed on the
unverified-app warning on the connect page). The Alert no longer
shouts with a leading icon when the friendly title + risk shield in
the header already carry the framing.
- Copy tweaks: "We couldn't double-check every step, so only continue
if you trust this app." / "This app is asking for unlimited access
to one of your tokens -- make sure you trust it." Tighter, no
trailing "here." filler.
- Continue button gets h=48px (matched to connect's Continue).
- Inspect details affordance moved to the same "label + link" pattern
the connect card uses for "Not you? Use another account":
"Want the technical details? Inspect ▾"
The Collapse expands inline below the link so the inspect tree
doesn't push the Continue button down the card.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
"Log in to VeChain" was ambiguous -- vague about whether the user is
logging in to VeChain.org, the kit, or some service. The actual
action is opening their VeChain wallet, so swap to "Log in to your
wallet": clearer, still short (5 words / 22 chars), and the
subtitle ("Sign in to grant access to [chip]") already names the
requester. The VeChain wordmark stays implicit in the logomark
above it.
VechainHeader's default title also updated for any place that
doesn't pass one explicitly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixup -- the previous commit only landed the explicit title prop in the loading state; the VechainHeader default still read "Log in to VeChain", so the show_picker phase (which uses the default) was still rendering the old copy. Sync the default now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…pedData_v4 Splits the transact request into a discriminated union (smart_account / typed_data / message) so the host can decode + show plain message text and arbitrary EIP-712 payloads in addition to the existing VeChain ExecuteWithAuthorization flow. Smart-account safety gates only fire for that kind; other requests just need the user to read what they're signing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
No description provided.