@@ -22,6 +22,7 @@ import {
2222import { asCredentialHash , LazorkitClient , getBlockchainTimestamp , SmartWalletAction } from './contract' ;
2323import { getFeePayer } from './core/paymaster' ;
2424import { sha256 } from 'js-sha256' ;
25+ import { SignAndSendTransactionPayload } from './types' ;
2526
2627/**
2728 * Connects to the wallet
@@ -46,7 +47,7 @@ export const connectAction = async (
4647
4748 try {
4849 const redirectUrl = options . redirectUrl ;
49- const connectUrl = `${ config . ipfsUrl } /${ API_ENDPOINTS . CONNECT
50+ const connectUrl = `${ config . portalUrl } /${ API_ENDPOINTS . CONNECT
5051 } &redirect_url=${ encodeURIComponent ( redirectUrl ) } `;
5152
5253 const resultUrl = await openBrowser ( connectUrl , redirectUrl ) ;
@@ -105,7 +106,7 @@ export const disconnectAction = async (set: (state: Partial<WalletStateClient>)
105106export const signAndExecuteTransaction = async (
106107 get : ( ) => WalletStateClient ,
107108 set : ( state : Partial < WalletStateClient > ) => void ,
108- instructions : anchor . web3 . TransactionInstruction [ ] ,
109+ payload : SignAndSendTransactionPayload ,
109110 options : SignOptions
110111) => {
111112 const { isSigning, connection, wallet, config } = get ( ) ;
@@ -134,20 +135,20 @@ export const signAndExecuteTransaction = async (
134135
135136 const feePayer = await getFeePayer ( config . configPaymaster . paymasterUrl , config . configPaymaster . apiKey ) ;
136137
137- const timestamp = await getBlockchainTimestamp ( connection ) ;
138+ const timestamp = new anchor . BN ( await getBlockchainTimestamp ( connection ) ) ;
138139
139140 const message = await lazorProgram . buildAuthorizationMessage ( {
140141 action : {
141142 type : SmartWalletAction . CreateChunk ,
142143 args : {
143144 policyInstruction : null ,
144- cpiInstructions : instructions ,
145+ cpiInstructions : payload . instructions ,
145146 }
146147 } ,
147148 payer : feePayer ,
148149 smartWallet : new anchor . web3 . PublicKey ( wallet . smartWallet ) ,
149150 passkeyPublicKey : wallet . passkeyPubkey ,
150- timestamp : new anchor . BN ( timestamp ) ,
151+ timestamp,
151152 credentialHash : asCredentialHash (
152153 Array . from (
153154 new Uint8Array (
@@ -162,11 +163,23 @@ export const signAndExecuteTransaction = async (
162163 . replace ( / \+ / g, '-' )
163164 . replace ( / \/ / g, '_' )
164165 . replace ( / = + $ / , '' ) ;
166+ const latestBlockhash = await connection . getLatestBlockhash ( ) ;
167+ const messageV0 = new anchor . web3 . TransactionMessage ( {
168+ payerKey : feePayer , // PublicKey
169+ recentBlockhash : latestBlockhash . blockhash ,
170+ instructions : payload . instructions ,
171+ } ) . compileToV0Message ( ) ;
165172
173+ const versionedTx = new anchor . web3 . VersionedTransaction ( messageV0 ) ;
174+ const base64Tx = Buffer . from ( versionedTx . serialize ( ) ) . toString ( "base64" ) ;
166175 const redirectUrl = options . redirectUrl ;
167- const signUrl = `${ config . ipfsUrl } /${ API_ENDPOINTS . SIGN } &message=${ encodeURIComponent (
176+ let signUrl = `${ config . portalUrl } /${ API_ENDPOINTS . SIGN } &message=${ encodeURIComponent (
168177 encodedChallenge
169- ) } &redirect_url=${ encodeURIComponent ( redirectUrl ) } `;
178+ ) } &credentialId=${ encodeURIComponent ( wallet . credentialId ) } &transaction=${ encodeURIComponent ( base64Tx ) } &redirect_url=${ encodeURIComponent ( redirectUrl ) } `;
179+
180+ if ( payload . transactionOptions ?. clusterSimulation ) {
181+ signUrl += `&clusterSimulation=${ payload . transactionOptions . clusterSimulation } ` ;
182+ }
170183
171184 await openSignBrowser (
172185 signUrl ,
@@ -188,11 +201,12 @@ export const signAndExecuteTransaction = async (
188201 type : SmartWalletAction . CreateChunk ,
189202 args : {
190203 policyInstruction : null ,
191- cpiInstructions : instructions ,
204+ cpiInstructions : payload . instructions ,
192205 }
193206 } ,
194207 browserResult ,
195- options
208+ options ,
209+ payload . transactionOptions
196210 ) ;
197211 options ?. onSuccess ?.( txnSignature ) ;
198212 } catch ( error ) {
@@ -220,3 +234,87 @@ export const signAndExecuteTransaction = async (
220234 set ( { isSigning : false } ) ;
221235 }
222236} ;
237+
238+ /**
239+ * Sign a message (arbitrary string or bytes)
240+ *
241+ * @param get - Zustand state getter function
242+ * @param set - Zustand state setter function
243+ * @param message - Message to sign (string)
244+ * @param options - Signing options with callbacks
245+ */
246+ export const signMessageAction = async (
247+ get : ( ) => WalletStateClient ,
248+ set : ( state : Partial < WalletStateClient > ) => void ,
249+ message : string ,
250+ options : SignOptions
251+ ) => {
252+ const { isSigning, wallet, config } = get ( ) ;
253+ if ( isSigning ) {
254+ return ;
255+ }
256+
257+ if ( ! wallet ) {
258+ const error = new SigningError ( 'No wallet connected' ) ;
259+ logger . error ( 'Sign message failed: No wallet connected' ) ;
260+ options ?. onFail ?.( error ) ;
261+ return ;
262+ }
263+
264+ set ( { isSigning : true , error : null } ) ;
265+
266+ try {
267+ const redirectUrl = options . redirectUrl ;
268+ // For signMessage, we pass the message directly.
269+ // The portal will treat 'transaction' param as message if it's not a valid transaction or if action is 'sign'
270+ // But better to use a specific param or just rely on 'transaction' param being the message container as per portal logic
271+ // PortalCommunicator: transaction: urlParams.get('transaction')
272+ // TransactionReview: portalParams.transaction || portalParams.message
273+ // Let's use 'message' param for clarity if portal supports it, checking portal-communicator.ts:
274+ // message: urlParams.get('message') || ''
275+ // So we should use 'message' param.
276+
277+ // If message is not base64, we might want to encode it?
278+ // User passes string. Let's pass it as is, or base64 encoded?
279+ // Portal expects 'message' to be displayed. If we want it to be readable, pass as string.
280+ // If it's bytes, pass base64?
281+ // The type signature says `message: string`. Let's assume readable string.
282+
283+ const signUrl = `${ config . portalUrl } /${ API_ENDPOINTS . SIGN } &message=${ encodeURIComponent (
284+ message
285+ ) } &credentialId=${ encodeURIComponent ( wallet . credentialId ) } &redirect_url=${ encodeURIComponent ( redirectUrl ) } `;
286+
287+ await openSignBrowser (
288+ signUrl ,
289+ redirectUrl ,
290+ async ( urlResult ) => {
291+ try {
292+ const authResult = handleBrowserResult ( urlResult ) ;
293+ const signature = authResult . signature ;
294+ const signedPayload = authResult . message ;
295+ options ?. onSuccess ?.( { signature, signedPayload } ) ;
296+ } catch ( error ) {
297+ const err = error instanceof Error ? error : new Error ( String ( error ) ) ;
298+ logger . error ( 'Sign message browser result processing failed:' , err , { urlResult } ) ;
299+ set ( { error : err } ) ;
300+ options ?. onFail ?.( err ) ;
301+ }
302+ } ,
303+ ( error ) => {
304+ logger . error ( 'Sign message browser failed:' , error , { signUrl, redirectUrl } ) ;
305+ set ( { error } ) ;
306+ options ?. onFail ?.( error ) ;
307+ }
308+ ) ;
309+ } catch ( error : unknown ) {
310+ logger . error ( 'Sign message action failed:' , error , {
311+ smartWallet : wallet ?. smartWallet ,
312+ redirectUrl : options . redirectUrl ,
313+ } ) ;
314+ const err = error instanceof Error ? error : new SigningError ( 'Unknown error' ) ;
315+ set ( { error : err } ) ;
316+ options ?. onFail ?.( err ) ;
317+ } finally {
318+ set ( { isSigning : false } ) ;
319+ }
320+ } ;
0 commit comments