1+ <script lang =" ts" >
2+ import { API } from " $lib/api" ;
3+ import { deployContract , switchToNetwork } from " $lib/ethereum" ;
4+ import type { ApprovalProcess , CreateApprovalProcessRequest } from " $lib/models/approval-process" ;
5+ import type { Artifact , DeployContractRequest , DeploymentResult , UpdateDeploymentRequest } from " $lib/models/deploy" ;
6+ import { getNetworkLiteral , isProductionNetwork } from " $lib/models/network" ;
7+ import { buildCompilerInput , type ContractSources } from " $lib/models/solc" ;
8+ import type { APIResponse } from " $lib/models/ui" ;
9+ import { addAPToDropdown , findDeploymentEnvironment , globalState } from " $lib/state/state.svelte" ;
10+ import { attempt } from " $lib/utils/attempt" ;
11+ import { encodeConstructorArgs , getConstructorInputsWizard , getContractBytecode } from " $lib/utils/contracts" ;
12+ import Button from " ./shared/Button.svelte" ;
13+ import Input from " ./shared/Input.svelte" ;
14+ import Message from " ./shared/Message.svelte" ;
15+
16+
17+ let inputsWithValue = $state <Record <string , string | number | boolean >>({});
18+ let busy = $state (false );
19+ let successMessage = $state <string >(" " );
20+ let errorMessage = $state <string >(" " );
21+ let compilationError = $state <string >(" " );
22+ let compilationResult = $state <{ output: Artifact [' output' ] }>();
23+ let deploymentId = $state <string | undefined >(undefined );
24+ let deploymentResult = $state <DeploymentResult | undefined >(undefined );
25+
26+ let contractBytecode = $derived .by (() => {
27+ if (! globalState .contract ?.target || ! compilationResult ) return ;
28+
29+ const name = globalState .contract .target ;
30+ const sources = compilationResult .output .contracts ;
31+
32+ return getContractBytecode (name , name , sources );
33+ });
34+
35+ let deploymentArtifact = $derived .by (() => {
36+ if (! compilationResult || ! globalState .contract ?.target || ! globalState .contract .source ?.sources ) return ;
37+
38+ return {
39+ input: buildCompilerInput (globalState .contract .source ?.sources as ContractSources ),
40+ output: compilationResult .output
41+ }
42+ });
43+
44+ let inputs = $derived .by (() => {
45+ if (! compilationResult ) return [];
46+ return getConstructorInputsWizard (globalState .contract ?.target , compilationResult .output .contracts );
47+ });
48+
49+ const deploymentUrl = $derived (
50+ deploymentId && globalState .form .network
51+ ? ` https://defender.openzeppelin.com/#/deploy/environment/${
52+ isProductionNetwork (globalState .form .network ) ? ' production' : ' test'
53+ }?deploymentId=${deploymentId } `
54+ : undefined
55+ );
56+
57+ $effect (() => {
58+ if (globalState .contract ?.source ?.sources ) {
59+ compile ();
60+ }
61+ });
62+
63+ function handleInputChange(event : Event ) {
64+ const target = event .target as HTMLInputElement ;
65+ inputsWithValue [target .name ] = target .value ;
66+ }
67+
68+ async function compile() {
69+ const sources = globalState .contract ?.source ?.sources ;
70+ if (! sources ) {
71+ return ;
72+ }
73+
74+ const [res, error] = await attempt (async () => API .compile (buildCompilerInput (
75+ globalState .contract ! .source ! .sources as ContractSources
76+ )));
77+
78+ if (error ) {
79+ compilationError = ` Compilation failed: ${error .msg } ` ;
80+ return ;
81+ }
82+ compilationResult = res .data ;
83+ }
84+
85+ const displayMessage = (message : string , type : " success" | " error" ) => {
86+ successMessage = " " ;
87+ errorMessage = " " ;
88+ if (type === " success" ) {
89+ successMessage = message ;
90+ } else {
91+ errorMessage = message ;
92+ }
93+ }
94+
95+ export async function handleInjectedProviderDeployment(bytecode : string ) {
96+ // Switch network if needed
97+ const [, networkError] = await attempt (async () => switchToNetwork (globalState .form .network ! ));
98+ if (networkError ) {
99+ throw new Error (` Error switching network: ${networkError .msg } ` );
100+ }
101+
102+ const [result, error] = await attempt (async () => deployContract (bytecode ));
103+ if (error ) {
104+ throw new Error (` Error deploying contract: ${error .msg } ` );
105+ }
106+
107+ if (! result ) {
108+ throw new Error (" Deployment result not found" );
109+ }
110+
111+ displayMessage (` Contract deployed successfully, hash: ${result ?.hash } ` , " success" );
112+
113+ return {
114+ address: result .address ,
115+ hash: result .hash ,
116+ sender: result .sender
117+ };
118+ }
119+
120+ async function getOrCreateApprovalProcess(): Promise <ApprovalProcess | undefined > {
121+ const ap = globalState .form .approvalProcessToCreate ;
122+ if (! ap || ! ap .via || ! ap .viaType ) {
123+ displayMessage (" Must select an approval process to create" , " error" );
124+ return ;
125+ }
126+
127+ if (! globalState .form .network ) {
128+ displayMessage (" Must select a network" , " error" );
129+ return ;
130+ }
131+
132+ const existing = findDeploymentEnvironment (ap .via , ap .network );
133+ if (existing ) {
134+ return existing ;
135+ }
136+
137+ const apRequest: CreateApprovalProcessRequest = {
138+ name: ` Deploy From Remix - ${ap .viaType } ` ,
139+ via: ap .via ,
140+ viaType: ap .viaType ,
141+ network: getNetworkLiteral (globalState .form .network ),
142+ relayerId: ap .relayerId ,
143+ component: [" deploy" ],
144+ };
145+ const result: APIResponse <{ approvalProcess: ApprovalProcess }> =
146+ await API .createApprovalProcess (apRequest );
147+
148+ if (! result .success ) {
149+ displayMessage (` Approval process creation failed, error: ${JSON .stringify (result .error )} ` , " error" );
150+ return ;
151+ }
152+
153+ displayMessage (" Deployment Environment successfully created" , " success" );
154+ if (! result .data ) return ;
155+
156+ addAPToDropdown (result .data .approvalProcess )
157+ return result .data .approvalProcess ;
158+ }
159+
160+ export async function createDefenderDeployment(request : DeployContractRequest ) {
161+ const result: APIResponse <{ deployment: { deploymentId: string } }> =
162+ await API .createDeployment (request );
163+
164+ if (! result .success || ! result .data ) {
165+ throw new Error (` Contract deployment creation failed: ${JSON .stringify (result .error )} ` );
166+ }
167+
168+ return result .data .deployment .deploymentId ;
169+ }
170+
171+ export async function updateDeploymentStatus(
172+ deploymentId : string ,
173+ address : string ,
174+ hash : string
175+ ) {
176+ const updateRequest: UpdateDeploymentRequest = {
177+ deploymentId ,
178+ hash ,
179+ address ,
180+ };
181+
182+ const result = await API .updateDeployment (updateRequest );
183+ if (! result .success ) {
184+ throw new Error (` Failed to update deployment status: ${JSON .stringify (result .error )} ` );
185+ }
186+ }
187+
188+ async function deploy() {
189+ if (! globalState .form .network ) {
190+ displayMessage (" No network selected" , " error" );
191+ return ;
192+ }
193+
194+ if (! globalState .contract ?.target || ! globalState .contract .source ?.sources ) {
195+ displayMessage (" No contract selected" , " error" );
196+ return ;
197+ }
198+
199+ if (! deploymentArtifact || ! contractBytecode ) {
200+ displayMessage (" No artifact found" , " error" );
201+ return ;
202+ }
203+
204+ const [constructorBytecode, constructorError] = await encodeConstructorArgs (inputs , inputsWithValue );
205+ if (constructorError ) {
206+ displayMessage (` Error encoding constructor arguments: ${constructorError .msg } ` , " error" );
207+ return ;
208+ }
209+
210+ // contract deployment requires contract bytecode
211+ // and constructor bytecode to be concatenated.
212+ const bytecode = contractBytecode + constructorBytecode ?.slice (2 );
213+
214+ const shouldUseInjectedProvider = globalState .form .approvalType === " injected" ;
215+ if (shouldUseInjectedProvider ) {
216+ const [result, error] = await attempt (async () =>
217+ handleInjectedProviderDeployment (bytecode ),
218+ );
219+ if (error ) {
220+ displayMessage (` Error deploying contract: ${error .msg } ` , " error" );
221+ return ;
222+ }
223+
224+ deploymentResult = result ;
225+
226+ // loads global state with EOA approval process to create
227+ globalState .form .approvalProcessToCreate = {
228+ viaType: " EOA" ,
229+ via: deploymentResult ?.sender ,
230+ network: getNetworkLiteral (globalState .form .network ),
231+ };
232+ globalState .form .approvalProcessSelected = undefined ;
233+ }
234+
235+ const approvalProcess = globalState .form .approvalProcessSelected ?? await getOrCreateApprovalProcess ();
236+ if (! approvalProcess ) {
237+ displayMessage (" No Approval Process selected" , " error" );
238+ return ;
239+ };
240+
241+ const deployRequest: DeployContractRequest = {
242+ network: getNetworkLiteral (globalState .form .network ),
243+ approvalProcessId: approvalProcess .approvalProcessId ,
244+ contractName: globalState .contract ! .target ,
245+ contractPath: globalState .contract ! .target ,
246+ verifySourceCode: true ,
247+ licenseType: ' MIT' ,
248+ artifactPayload: JSON .stringify (deploymentArtifact ),
249+ // TODO: Implement constructor arguments + salt
250+ constructorBytecode: ' ' ,
251+ salt: ' ' ,
252+ }
253+
254+ const [newDeploymentId, deployError] = await attempt (async () => createDefenderDeployment (deployRequest ));
255+ if (deployError || ! newDeploymentId ) {
256+ displayMessage (` Deployment failed to create: ${deployError ?.msg } ` , " error" );
257+ return ;
258+ }
259+
260+ if (shouldUseInjectedProvider && deploymentResult ) {
261+ const [, updateError] = await attempt (async () => updateDeploymentStatus (
262+ newDeploymentId ,
263+ deploymentResult ! .address ,
264+ deploymentResult ! .hash
265+ ));
266+ if (updateError ) {
267+ displayMessage (` Error updating deployment status: ${updateError .msg } ` , " error" );
268+ return ;
269+ }
270+ } else {
271+ // If we're not using an injected provider
272+ // we need to listen for the deployment to be finished.
273+ // listenForDeployment(newDeploymentId);
274+ }
275+
276+ deploymentId = newDeploymentId ;
277+ displayMessage (" Deployment successfuly created in Defender" , " success" );
278+ };
279+
280+ async function triggerDeploy() {
281+ busy = true ;
282+ await deploy ();
283+ busy = false ;
284+ }
285+
286+ </script >
287+
288+ <div class =" px-4 flex flex-col gap-2" >
289+ {#if compilationError }
290+ <Message message ={compilationError } type =" error" />
291+ {/if }
292+
293+ {#if inputs .length > 0 }
294+ <h6 class =" text-sm" >Constructor Arguments</h6 >
295+ {#each inputs as input }
296+ <Input name ={input .name } placeholder ={input .name } onchange ={handleInputChange } value ={' ' } type =" text" />
297+ {/each }
298+ {/if }
299+
300+ <Button disabled ={! globalState .authenticated || busy } loading ={busy } label ="Deploy" onClick ={triggerDeploy } />
301+
302+ {#if successMessage || errorMessage }
303+ <Message message ={successMessage || errorMessage } type ={successMessage ? " success" : " error" } />
304+
305+ {#if deploymentUrl }
306+ <Button label ={" View Deployment" } onClick ={() => window .open (deploymentUrl , " _blank" )} type =" secondary" />
307+ {/if }
308+ {/if }
309+ </div >
0 commit comments