@@ -138,44 +138,79 @@ along with a transfer of 0.01 USDC to an address on Base Sepolia.
138138``` tsx [app/page.tsx]
139139" use client" ;
140140
141- import { ProviderInterface } from " @coinbase/wallet-sdk" ;
142141import { useEffect , useState } from " react" ;
143142import { encodeFunctionData , erc20Abi , numberToHex , parseUnits } from " viem" ;
144- import { useAccount , useConnect , useDisconnect } from " wagmi" ;
143+ import { useConnect , useSendCalls } from " wagmi" ;
144+
145+ interface DataRequest {
146+ email: boolean ;
147+ address: boolean ;
148+ }
149+
150+ interface ProfileResult {
151+ success: boolean ;
152+ email? : string ;
153+ address? : string ;
154+ error? : string ;
155+ }
145156
146157export default function Home() {
147- const [provider, setProvider] = useState (undefined );
148- const [dataToRequest, setDataToRequest] = useState ({
158+ const [dataToRequest, setDataToRequest] = useState <DataRequest >({
149159 email: true ,
150160 address: true
151161 });
152- const [isLoading, setIsLoading] = useState (false );
153- const [result, setResult] = useState (null );
162+ const [result, setResult] = useState <ProfileResult | null >(null );
154163
155- const account = useAccount ();
156- const { connectors, connect, status, error } = useConnect ();
157- const { disconnect } = useDisconnect ();
164+ const { sendCalls, data, error, isPending } = useSendCalls ();
165+ const { connect, connectors } = useConnect ()
158166
159- // Initialize provider when account connected
160- useEffect (() => {
161- async function getProvider() {
162- if (account .status === ' connected' && account .connector ) {
163- const provider = await account .connector .getProvider ();
164- setProvider (provider );
165- }
166- }
167- getProvider ();
168- }, [account ]);
169167
170168 // Function to get callback URL - replace in production
171169 function getCallbackURL() {
172170 return " https://your-ngrok-url.ngrok-free.app/api/data-validation" ;
173171 }
174172
173+ // Handle response data when sendCalls completes
174+ useEffect (() => {
175+ if (data ?.capabilities ?.dataCallback ) {
176+ const callbackData = data .capabilities .dataCallback ;
177+ const newResult: ProfileResult = { success: true };
178+
179+ // Extract email if provided
180+ if (callbackData .email ) newResult .email = callbackData .email ;
181+
182+ // Extract address if provided
183+ if (callbackData .physicalAddress ) {
184+ const addr = callbackData .physicalAddress .physicalAddress ;
185+ newResult .address = [
186+ addr .address1 ,
187+ addr .address2 ,
188+ addr .city ,
189+ addr .state ,
190+ addr .postalCode ,
191+ addr .countryCode
192+ ].filter (Boolean ).join (" , " );
193+ }
194+
195+ setResult (newResult );
196+ } else if (data && ! data .capabilities ?.dataCallback ) {
197+ setResult ({ success: false , error: " Invalid response - no data callback" });
198+ }
199+ }, [data ]);
200+
201+ // Handle errors
202+ useEffect (() => {
203+ if (error ) {
204+ setResult ({
205+ success: false ,
206+ error: error .message || " Transaction failed"
207+ });
208+ }
209+ }, [error ]);
210+
175211 // Handle form submission
176212 async function handleSubmit() {
177213 try {
178- setIsLoading (true );
179214 setResult (null );
180215
181216 // Build requests array based on checkboxes
@@ -185,125 +220,79 @@ export default function Home() {
185220
186221 if (requests .length === 0 ) {
187222 setResult ({ success: false , error: " Select at least one data type" });
188- setIsLoading (false );
189223 return ;
190224 }
191225
192- // Request data from wallet
193- const response = await provider ?.request ({
194- method: " wallet_sendCalls" ,
195- params: [{
196- version: " 1.0" ,
197- chainId: numberToHex (84532 ), // Base Sepolia
198- calls: [
199- {
200- to: " 0x036CbD53842c5426634e7929541eC2318f3dCF7e" , // USDC contract address on Base Sepolia
201- data: encodeFunctionData ({
202- abi: erc20Abi ,
203- functionName: " transfer" ,
204- args: [
205- " 0xd8da6bf26964af9d7eed9e03e53415d37aa96045" ,
206- parseUnits (" 0.01" , 6 ),
207- ],
208- }),
209- },
210- ], // Simple transfer of 0.01 USDC to the contract
211- capabilities: {
212- dataCallback: {
213- requests: requests ,
214- callbackURL: getCallbackURL (),
215- },
226+ // Send calls using wagmi hook
227+ sendCalls ({
228+ connector: connectors [0 ],
229+ account: null ,
230+ calls: [
231+ {
232+ to: " 0x036CbD53842c5426634e7929541eC2318f3dCF7e" , // USDC contract address on Base Sepolia
233+ data: encodeFunctionData ({
234+ abi: erc20Abi ,
235+ functionName: " transfer" ,
236+ args: [
237+ " 0xd8da6bf26964af9d7eed9e03e53415d37aa96045" ,
238+ parseUnits (" 0.01" , 6 ),
239+ ],
240+ }),
241+ },
242+ ],
243+ chainId: 84532 , // Base Sepolia
244+ capabilities: {
245+ dataCallback: {
246+ requests: requests ,
247+ callbackURL: getCallbackURL (),
216248 },
217- }],
249+ },
250+ });
251+ } catch (err ) {
252+ setResult ({
253+ success: false ,
254+ error: err instanceof Error ? err .message : " Unknown error occurred"
218255 });
219-
220- // Process response
221- if (response ?.capabilities ?.dataCallback ) {
222- const data = response .capabilities .dataCallback ;
223- const result = { success: true };
224-
225- // Extract email if provided
226- if (data .email ) result .email = data .email ;
227-
228- // Extract address if provided
229- if (data .physicalAddress ) {
230- const addr = data .physicalAddress .physicalAddress ;
231- result .address = [
232- addr .address1 ,
233- addr .address2 ,
234- addr .city ,
235- addr .state ,
236- addr .postalCode ,
237- addr .countryCode
238- ].filter (Boolean ).join (" , " );
239- }
240-
241- setResult (result );
242- } else {
243- setResult ({ success: false , error: " Invalid response" });
244- }
245- } catch (error ) {
246- setResult ({ success: false , error: error .message || " Transaction failed" });
247- } finally {
248- setIsLoading (false );
249256 }
250257 }
251258
252259 return (
253260 <div style = { { maxWidth: " 600px" , margin: " 0 auto" , padding: " 20px" }} >
254261 <h1 >Profiles Demo</h1 >
255262
256- { /* Wallet Status */ }
257- <div style = { { marginBottom: " 20px" }} >
258- <p >Status: { account .status } </p >
259- { account .status === ' connected' ? (
260- <button onClick = { disconnect } >Disconnect</button >
261- ) : (
262- connectors
263- .filter (c => c .name === ' Coinbase Wallet' )
264- .map (connector => (
265- <button key = { connector .uid } onClick = { () => connect ({ connector })} >
266- Connect Smart Wallet
267- </button >
268- ))
269- )}
270- </div >
271-
272263 { /* Data Request Form */ }
273- { account .status === ' connected' && (
274- <div style = { { marginTop: " 20px" }} >
275- <h2 >Checkout</h2 >
276-
277- <div >
278- <label >
279- <input
280- type = " checkbox"
281- checked = { dataToRequest .email }
282- onChange = { () => setDataToRequest (prev => ({ ... prev , email: ! prev .email }))}
283- />
284- Email Address
285- </label >
286- </div >
287-
288- <div >
289- <label >
290- <input
291- type = " checkbox"
292- checked = { dataToRequest .address }
293- onChange = { () => setDataToRequest (prev => ({ ... prev , address: ! prev .address }))}
294- />
295- Physical Address
296- </label >
297- </div >
298-
299- <button
300- onClick = { handleSubmit }
301- disabled = { isLoading || ! provider }
302- >
303- { isLoading ? " Processing..." : " Checkout" }
304- </button >
264+ <div style = { { marginTop: " 20px" }} >
265+ <h2 >Checkout</h2 >
266+
267+ <div >
268+ <label >
269+ <input
270+ type = " checkbox"
271+ checked = { dataToRequest .email }
272+ onChange = { () => setDataToRequest (prev => ({ ... prev , email: ! prev .email }))}
273+ />
274+ Email Address
275+ </label >
305276 </div >
306- )}
277+
278+ <div >
279+ <label >
280+ <input
281+ type = " checkbox"
282+ checked = { dataToRequest .address }
283+ onChange = { () => setDataToRequest (prev => ({ ... prev , address: ! prev .address }))}
284+ />
285+ Physical Address
286+ </label >
287+ </div >
288+
289+ <button
290+ onClick = { handleSubmit }
291+ disabled = { isPending }
292+ >
293+ { isPending ? " Processing..." : " Checkout" }
294+ </button >
295+ </div >
307296
308297 { /* Results Display */ }
309298 { result && (
@@ -355,6 +344,16 @@ You can learn more about ngrok [here](https://ngrok.com/docs).
355344
356345</Callout >
357346
347+ <Callout type = " warning" >
348+ ** Important caveats about the validation API**
349+
350+ If the API validation check is successful, you MUST return the original calls in the response of the API endpoint as it's shown in the [ validation API section] ( #implementing-the-validation-api ) .
351+ If you don't, the wallet will return an error.
352+
353+ You can also return a new set of calls to be made after the data is validated.
354+
355+ </Callout >
356+
358357### Implementing the Validation API
359358
360359Now, let's create an API endpoint to validate the data received from Smart Wallet.
@@ -414,9 +413,11 @@ export async function POST(request: Request) {
414413
415414 // Success - no validation errors - you HAVE to return the original calls
416415 return Response .json ({
416+ request: {
417417 calls: requestData .calls ,
418418 chainId: requestData .chainId ,
419- capabilities: requestData .capabilities
419+ version: requestData .version ,
420+ },
420421 });
421422
422423 } catch (error ) {
0 commit comments