@@ -9,12 +9,19 @@ import {
99 ensureActiveWallet ,
1010 ensureChainConfigured ,
1111 handleCommandError ,
12- promptPassword ,
12+ isNonInteractiveMode ,
13+ outputSuccess ,
14+ outputError ,
1315} from '../../utils/command-helpers.js'
1416import { selectTransaction } from '../../utils/safe-helpers.js'
17+ import { getPassword } from '../../utils/password-handler.js'
18+ import { getGlobalOptions } from '../../types/global-options.js'
19+ import { ExitCode } from '../../constants/exit-codes.js'
1520
1621export async function executeTransaction ( safeTxHash ?: string ) {
17- p . intro ( 'Execute Safe Transaction' )
22+ if ( ! isNonInteractiveMode ( ) ) {
23+ p . intro ( 'Execute Safe Transaction' )
24+ }
1825
1926 try {
2027 const ctx = createCommandContext ( )
@@ -26,6 +33,9 @@ export async function executeTransaction(safeTxHash?: string) {
2633 let selectedSafeTxHash = safeTxHash
2734
2835 if ( ! selectedSafeTxHash ) {
36+ if ( isNonInteractiveMode ( ) ) {
37+ outputError ( 'Transaction hash is required in non-interactive mode' , ExitCode . INVALID_ARGS )
38+ }
2939 const hash = await selectTransaction (
3040 ctx . transactionStore ,
3141 ctx . safeStorage ,
@@ -39,38 +49,30 @@ export async function executeTransaction(safeTxHash?: string) {
3949
4050 const transaction = ctx . transactionStore . getTransaction ( selectedSafeTxHash )
4151 if ( ! transaction ) {
42- p . log . error ( `Transaction ${ selectedSafeTxHash } not found` )
43- p . outro ( 'Failed' )
44- return
52+ outputError ( `Transaction ${ selectedSafeTxHash } not found` , ExitCode . ERROR )
4553 }
4654
4755 if ( transaction . status === TransactionStatus . EXECUTED ) {
48- p . log . error ( 'Transaction already executed' )
49- p . outro ( 'Failed' )
50- return
56+ outputError ( 'Transaction already executed' , ExitCode . ERROR )
5157 }
5258
5359 if ( transaction . status === 'rejected' ) {
54- p . log . error ( 'Transaction has been rejected' )
55- p . outro ( 'Failed' )
56- return
60+ outputError ( 'Transaction has been rejected' , ExitCode . ERROR )
5761 }
5862
5963 // Get Safe info
6064 const safe = ctx . safeStorage . getSafe ( transaction . chainId , transaction . safeAddress )
6165 if ( ! safe ) {
62- p . log . error ( 'Safe not found' )
63- p . outro ( 'Failed' )
64- return
66+ outputError ( 'Safe not found' , ExitCode . SAFE_NOT_FOUND )
6567 }
6668
6769 // Get chain
6870 const chain = ensureChainConfigured ( transaction . chainId , ctx . configStore )
6971 if ( ! chain ) return
7072
7173 // Fetch live owners and threshold from blockchain
72- const spinner = p . spinner ( )
73- spinner . start ( 'Fetching Safe information from blockchain...' )
74+ const spinner = ! isNonInteractiveMode ( ) ? p . spinner ( ) : null
75+ spinner ? .start ( 'Fetching Safe information from blockchain...' )
7476
7577 let owners : Address [ ]
7678 let threshold : number
@@ -80,65 +82,71 @@ export async function executeTransaction(safeTxHash?: string) {
8082 txService . getOwners ( transaction . safeAddress ) ,
8183 txService . getThreshold ( transaction . safeAddress ) ,
8284 ] )
83- spinner . stop ( 'Safe information fetched' )
85+ spinner ? .stop ( 'Safe information fetched' )
8486 } catch ( error ) {
85- spinner . stop ( 'Failed to fetch Safe information' )
86- p . log . error (
87- error instanceof Error ? error . message : 'Failed to fetch Safe data from blockchain'
87+ spinner ?. stop ( 'Failed to fetch Safe information' )
88+ outputError (
89+ error instanceof Error ? error . message : 'Failed to fetch Safe data from blockchain' ,
90+ ExitCode . NETWORK_ERROR
8891 )
89- p . outro ( 'Failed' )
90- return
9192 }
9293
9394 // Check if wallet is an owner
9495 if ( ! owners . some ( ( owner ) => owner . toLowerCase ( ) === activeWallet . address . toLowerCase ( ) ) ) {
95- p . log . error ( 'Active wallet is not an owner of this Safe' )
96- p . outro ( 'Failed' )
97- return
96+ outputError ( 'Active wallet is not an owner of this Safe' , ExitCode . ERROR )
9897 }
9998
10099 // Check if we have enough signatures
101100 const sigCount = transaction . signatures ?. length || 0
102101 if ( sigCount < threshold ) {
103- p . log . error ( `Not enough signatures. Have ${ sigCount } , need ${ threshold } ` )
104- p . outro ( 'Failed' )
105- return
102+ outputError ( `Not enough signatures. Have ${ sigCount } , need ${ threshold } ` , ExitCode . ERROR )
106103 }
107104
108- // Display transaction details
109- console . log ( '\nTransaction Details:' )
110- console . log ( ` To: ${ transaction . metadata . to } ` )
111- console . log ( ` Value: ${ transaction . metadata . value } wei` )
112- console . log ( ` Data: ${ transaction . metadata . data } ` )
113- console . log ( ` Operation: ${ transaction . metadata . operation === 0 ? 'Call' : 'DelegateCall' } ` )
114- console . log ( ` Signatures: ${ sigCount } /${ threshold } ` )
115-
116- const confirm = await p . confirm ( {
117- message : 'Execute this transaction on-chain?' ,
118- initialValue : false ,
119- } )
120-
121- if ( ! confirm || p . isCancel ( confirm ) ) {
122- p . cancel ( 'Operation cancelled' )
123- return
105+ if ( ! isNonInteractiveMode ( ) ) {
106+ // Display transaction details
107+ console . log ( '\nTransaction Details:' )
108+ console . log ( ` To: ${ transaction . metadata . to } ` )
109+ console . log ( ` Value: ${ transaction . metadata . value } wei` )
110+ console . log ( ` Data: ${ transaction . metadata . data } ` )
111+ console . log ( ` Operation: ${ transaction . metadata . operation === 0 ? 'Call' : 'DelegateCall' } ` )
112+ console . log ( ` Signatures: ${ sigCount } /${ threshold } ` )
113+
114+ const confirm = await p . confirm ( {
115+ message : 'Execute this transaction on-chain?' ,
116+ initialValue : false ,
117+ } )
118+
119+ if ( ! confirm || p . isCancel ( confirm ) ) {
120+ p . cancel ( 'Operation cancelled' )
121+ return
122+ }
124123 }
125124
126- // Request password
127- const password = await promptPassword ( false , 'Enter wallet password' )
128- if ( ! password ) return
125+ // Request password using centralized handler
126+ const globalOptions = getGlobalOptions ( )
127+ const password = await getPassword (
128+ {
129+ password : globalOptions . password ,
130+ passwordFile : globalOptions . passwordFile ,
131+ passwordEnv : 'SAFE_WALLET_PASSWORD' ,
132+ } ,
133+ 'Enter wallet password'
134+ )
135+
136+ if ( ! password ) {
137+ outputError ( 'Password is required' , ExitCode . AUTH_FAILURE )
138+ }
129139
130140 // Get private key
131- const spinner2 = p . spinner ( )
132- spinner2 . start ( 'Executing transaction' )
141+ const spinner2 = ! isNonInteractiveMode ( ) ? p . spinner ( ) : null
142+ spinner2 ? .start ( 'Executing transaction' )
133143
134144 let privateKey : string
135145 try {
136146 privateKey = ctx . walletStorage . getPrivateKey ( activeWallet . id , password )
137147 } catch {
138- spinner2 . stop ( 'Failed' )
139- p . log . error ( 'Invalid password' )
140- p . outro ( 'Failed' )
141- return
148+ spinner2 ?. stop ( 'Failed' )
149+ outputError ( 'Invalid password' , ExitCode . AUTH_FAILURE )
142150 }
143151
144152 // Execute transaction
@@ -156,14 +164,24 @@ export async function executeTransaction(safeTxHash?: string) {
156164 // Update transaction status
157165 ctx . transactionStore . updateStatus ( selectedSafeTxHash , TransactionStatus . EXECUTED , txHash )
158166
159- spinner2 . stop ( 'Transaction executed' )
167+ spinner2 ? .stop ( 'Transaction executed' )
160168
161169 const explorerUrl = chain . explorer ? `${ chain . explorer } /tx/${ txHash } ` : undefined
162170
163- await renderScreen ( TransactionExecuteSuccessScreen , {
164- txHash,
165- explorerUrl,
166- } )
171+ if ( isNonInteractiveMode ( ) ) {
172+ outputSuccess ( 'Transaction executed successfully' , {
173+ safeTxHash : selectedSafeTxHash ,
174+ txHash,
175+ explorerUrl,
176+ chainId : transaction . chainId ,
177+ chainName : chain . name ,
178+ } )
179+ } else {
180+ await renderScreen ( TransactionExecuteSuccessScreen , {
181+ txHash,
182+ explorerUrl,
183+ } )
184+ }
167185 } catch ( error ) {
168186 handleCommandError ( error )
169187 }
0 commit comments