Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/deploy-ai-credits-web.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ on:
paths:
- 'apps/ai-credits-web/**'
- 'packages/ai-credits-widget/**'
- 'packages/core/**'
- 'packages/ui/**'
- 'packages/embed/**'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
pull_request_target:
types: [opened, synchronize, edited, ready_for_review]
branches:
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/deploy-superfluid-campaign-web.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ on:
paths:
- 'apps/superfluid-campaign-web/**'
- 'packages/superfluid-campaign-widget/**'
- 'packages/citizen-claim-widget/**'
- 'packages/core/**'
- 'packages/ui/**'
- 'packages/embed/**'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
pull_request_target:
types: [opened, synchronize, edited, ready_for_review]
branches:
Expand Down
19 changes: 1 addition & 18 deletions packages/citizen-claim-widget/src/CitizenClaimWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,13 +252,7 @@ function CitizenClaimInner({
})

try {
const receipt = await actions.claim(() =>
updateToast(toastId, {
message: `Claiming on ${singleChainName} — waiting for blockchain confirmation`,
status: 'confirming',
duration: 0,
}),
)
const receipt = await actions.claim()
Comment on lines 254 to +255
updateToast(toastId, {
message: `Claim succeeded on ${singleChainName}`,
status: 'success',
Expand Down Expand Up @@ -302,17 +296,6 @@ function CitizenClaimInner({

const claimResults = await actions.claimAll(
claimPlan.map((entry) => entry.chainId),
(submittedChainId) => {
const toastId = toastByChain.get(submittedChainId)
if (!toastId) return
const entryChainName =
chainNameById.get(submittedChainId) ?? getChainDisplayName(submittedChainId)
updateToast(toastId, {
message: `Claiming on ${entryChainName} — waiting for blockchain confirmation`,
status: 'confirming',
duration: 0,
})
},
)
Comment on lines 297 to 299

for (const claimResult of claimResults) {
Expand Down
57 changes: 12 additions & 45 deletions packages/citizen-claim-widget/src/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,35 +60,6 @@ const CHAIN_CONFIGS: Record<number, Chain> = {
} as Chain,
}

/**
* Wraps an EIP-1193 provider so `onTransactionSubmitted` fires the instant a
* wallet finishes signing and broadcasting a transaction (its
* `eth_sendTransaction` call resolving), before viem's receipt polling even
* starts. citizen-sdk's public claim() has no equivalent mid-flight signal,
* but GoodWidget already owns this provider before handing it to viem's
* `custom()` transport, so it can observe that moment itself with no SDK
* change required.
*/
function wrapProviderWithSubmissionSignal(
provider: EIP1193Provider,
onTransactionSubmitted: () => void,
): EIP1193Provider {
return new Proxy(provider, {
get(target, property, receiver) {
if (property === 'request') {
return async (args: Parameters<EIP1193Provider['request']>[0]) => {
const result = await target.request(args)
if (args.method === 'eth_sendTransaction') {
onTransactionSubmitted()
}
return result
}
}
return Reflect.get(target, property, receiver)
},
})
}

const SUPPORTED_CHAINS = citizenSdkCapabilities.chains
const AVAILABLE_ENVIRONMENTS = citizenSdkCapabilities.environments

Expand Down Expand Up @@ -285,14 +256,11 @@ export function useCitizenClaimAdapter(
// available for integrations that prefer lazy per-chain client creation.
// ---------------------------------------------------------------------------
const createProviderClientsForChain = useCallback(
(targetChainId: number, onTransactionSubmitted?: () => void) => {
(targetChainId: number) => {
if (!provider || !address) return null
const chain = CHAIN_CONFIGS[targetChainId]
if (!chain) return null
const effectiveProvider = onTransactionSubmitted
? wrapProviderWithSubmissionSignal(provider, onTransactionSubmitted)
: provider
const transport = custom(effectiveProvider as Parameters<typeof custom>[0])
const transport = custom(provider as Parameters<typeof custom>[0])
const publicClient = createPublicClient({ chain, transport })
const walletClient = createWalletClient({
account: address as `0x${string}`,
Expand Down Expand Up @@ -346,7 +314,7 @@ export function useCitizenClaimAdapter(
)

const resolveClientsForChain = useCallback(
async (targetChainId: number, onTransactionSubmitted?: () => void) => {
async (targetChainId: number) => {
if (isCustodialExecution) {
const configuredClients = claimExecution?.clientsByChain[targetChainId]
if (configuredClients) return normalizeClientBundle(configuredClients)
Expand All @@ -367,7 +335,7 @@ export function useCitizenClaimAdapter(
return normalizeClientBundle(factoryClients)
}

return normalizeClientBundle(createProviderClientsForChain(targetChainId, onTransactionSubmitted))
return normalizeClientBundle(createProviderClientsForChain(targetChainId))
},
[
address,
Expand Down Expand Up @@ -411,8 +379,8 @@ export function useCitizenClaimAdapter(
)

const createSdkInstancesForChain = useCallback(
async (targetChainId: number, onTransactionSubmitted?: () => void) => {
const clients = await resolveClientsForChain(targetChainId, onTransactionSubmitted)
async (targetChainId: number) => {
const clients = await resolveClientsForChain(targetChainId)
return createSdkInstances(clients)
},
[createSdkInstances, resolveClientsForChain],
Expand Down Expand Up @@ -684,7 +652,7 @@ export function useCitizenClaimAdapter(
// Transitions: eligible → claiming → success | error
// ---------------------------------------------------------------------------
const claimOnChain = useCallback(
async (targetChainId: number, onTransactionSubmitted?: () => void): Promise<unknown> => {
async (targetChainId: number): Promise<unknown> => {
if (!isCustodialExecution && !provider) {
throw new CitizenClaimAdapterError('No wallet provider available')
}
Expand Down Expand Up @@ -719,7 +687,7 @@ export function useCitizenClaimAdapter(
await switchChain(targetChainId)
}

const sdk = await createSdkInstancesForChain(targetChainId, onTransactionSubmitted)
const sdk = await createSdkInstancesForChain(targetChainId)
if (!sdk) {
throw new CitizenClaimAdapterError(
`Unable to initialize SDK clients for ${getChainDisplayName(targetChainId)}`,
Expand Down Expand Up @@ -760,15 +728,14 @@ export function useCitizenClaimAdapter(
const claimAll = useCallback(
async (
targetChainIds: number[],
onTransactionSubmitted?: (chainId: number) => void,
): Promise<CitizenClaimWidgetChainClaimResult[]> => {
const chainIdsToClaim = [...new Set(targetChainIds)]

if (isCustodialExecution) {
const settled = await Promise.allSettled(
chainIdsToClaim.map(async (targetChainId) => ({
chainId: targetChainId,
receipt: await claimOnChain(targetChainId, () => onTransactionSubmitted?.(targetChainId)),
receipt: await claimOnChain(targetChainId),
})),
)

Expand All @@ -793,7 +760,7 @@ export function useCitizenClaimAdapter(
results.push({
chainId: targetChainId,
status: 'fulfilled',
receipt: await claimOnChain(targetChainId, () => onTransactionSubmitted?.(targetChainId)),
receipt: await claimOnChain(targetChainId),
})
} catch (claimError: unknown) {
results.push({
Expand All @@ -808,14 +775,14 @@ export function useCitizenClaimAdapter(
[claimOnChain, isCustodialExecution],
)

const handleClaim = useCallback(async (onTransactionSubmitted?: () => void): Promise<unknown> => {
const handleClaim = useCallback(async (): Promise<unknown> => {
if (!chainId) throw new Error('No active chain selected')

setStatus('claiming')
setError(null)

try {
const receipt = await claimOnChain(chainId, onTransactionSubmitted)
const receipt = await claimOnChain(chainId)
if (!mountedRef.current) return receipt
await loadClaimStatus()
return receipt
Expand Down
10 changes: 2 additions & 8 deletions packages/citizen-claim-widget/src/widgetRuntimeContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,10 @@ export interface CitizenClaimWidgetAdapterActions {
connect: () => Promise<void>
refresh: () => Promise<void>
startVerification: () => Promise<void>
/**
* `onTransactionSubmitted` fires once the wallet has signed and broadcast
* the transaction, ahead of on-chain confirmation — lets callers move a
* "sign in your wallet" toast to a "waiting for confirmation" state.
*/
claim: (onTransactionSubmitted?: () => void) => Promise<unknown>
claimOnChain: (chainId: number, onTransactionSubmitted?: () => void) => Promise<unknown>
claim: () => Promise<unknown>
claimOnChain: (chainId: number) => Promise<unknown>
claimAll: (
chainIds: number[],
onTransactionSubmitted?: (chainId: number) => void,
) => Promise<CitizenClaimWidgetChainClaimResult[]>
Comment on lines +71 to 75
switchChain?: (chainId: number) => Promise<void>
}
Expand Down
13 changes: 5 additions & 8 deletions packages/ui/src/components/Toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Spinner } from '../components-test/Spinner'
// Multiple toasts can be visible at once; each is identified by a unique id.
// ---------------------------------------------------------------------------

export type ToastStatus = 'pending' | 'confirming' | 'success' | 'error' | 'info'
export type ToastStatus = 'pending' | 'success' | 'error' | 'info'

export interface ToastConfig {
message: string
Expand Down Expand Up @@ -83,11 +83,10 @@ export function useToast(): ToastItem[] {
* Named 'Toast' so Tamagui resolves light_Toast / dark_Toast component themes.
*
* Status variant adjusts the border accent color to communicate the toast type:
* pending → primary (blue) — waiting on the wallet to sign
* confirming → primaryDark (deeper blue) — signed and broadcast, waiting on-chain
* success → success (green)
* error → error (red)
* info → primary (blue)
* pending → primary (blue) — waiting on the wallet to sign
* success → success (green)
* error → error (red)
* info → primary (blue)
*/
const ToastFrame = createComponent(Stack, {
name: 'Toast',
Expand All @@ -109,7 +108,6 @@ const ToastFrame = createComponent(Stack, {
variants: {
status: {
pending: { borderColor: '$primary' },
confirming: { borderColor: '$primaryDark' },
success: { borderColor: '$success' },
error: { borderColor: '$error' },
info: { borderColor: '$primary' },
Expand Down Expand Up @@ -163,7 +161,6 @@ function StatusIcon({ status }: { status?: ToastStatus }) {
if (!status) return null
switch (status) {
case 'pending':
case 'confirming':
return <Spinner size="sm" />
case 'success':
return <Icon name="check" size="xs" color="success" />
Expand Down
Loading