Skip to content

Commit b7dd07e

Browse files
committed
add deployment logic
1 parent eff7e92 commit b7dd07e

File tree

5 files changed

+217
-24
lines changed

5 files changed

+217
-24
lines changed

src/lib/remix/components/Deploy.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
contractBytecode = getContractBytecode(
8282
contractInfo.path,
8383
contractInfo.name,
84-
globalState.contract.data
84+
globalState.contract.data.contracts
8585
);
8686
}
8787
});

src/lib/utils/contracts.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export function createArtifactPayload(
6767
export function getContractBytecode(
6868
path: string,
6969
contractName: string,
70-
compilation: CompilationResult
70+
contractSources: Record<string, any>
7171
): string {
72-
return compilation.contracts[path][contractName].evm.bytecode.object;
72+
return contractSources[path][contractName].evm.bytecode.object;
7373
}

src/lib/wizard/components/ApprovalProcess.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import { getNetworkLiteral } from "$lib/models/network";
99
import Input from "./shared/Input.svelte";
1010
11-
let address = $state<string>("");
11+
let address = $state<string>(globalState.form.approvalProcessToCreate?.via || "");
1212
1313
function approvalProcessByNetworkAndComponent(ap: ApprovalProcess) {
1414
const networkName = typeof globalState.form.network === 'string'
@@ -163,7 +163,7 @@
163163

164164
{#if approvalProcessType === "EOA" || approvalProcessType === "Safe"}
165165
<div class="mt-2">
166-
<Input value={address} placeholder="* Address" type="text" />
166+
<Input value={address} placeholder="* Address" type="text" onchange={onAddressChange} />
167167
</div>
168168
{:else if approvalProcessType === "Relayer"}
169169
{#if disableRelayers}

src/lib/wizard/components/shared/Input.svelte

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
<script lang="ts">
2+
import type { ChangeEventHandler } from "svelte/elements";
3+
24
type Props = {
35
label?: string;
46
value: string;
57
placeholder: string;
68
type: 'text' | 'password';
79
disabled?: boolean;
10+
onchange?: ChangeEventHandler<HTMLInputElement>;
811
};
9-
let { label, value = $bindable(), placeholder, type, disabled }: Props = $props();
12+
let { label, value = $bindable(), placeholder, type, disabled, onchange }: Props = $props();
1013
1114
</script>
1215

@@ -19,5 +22,6 @@
1922
bind:value={value}
2023
placeholder={placeholder}
2124
disabled={disabled}
25+
onchange={onchange}
2226
class="border text-xs border-gray-300 disabled:opacity-50 rounded-md p-2 w-full"
2327
/>

src/routes/wizard.svelte

Lines changed: 207 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,33 @@
55
import { onMount } from "svelte";
66
import Configuration from "$lib/wizard/components/Configuration.svelte";
77
import Network from "$lib/wizard/components/Network.svelte";
8-
import ApprovalProcess from "$lib/wizard/components/ApprovalProcess.svelte";
98
import Button from "$lib/wizard/components/shared/Button.svelte";
10-
import { globalState } from "$lib/state/state.svelte";
9+
import { addAPToDropdown, globalState } from "$lib/state/state.svelte";
1110
import { API } from "$lib/api";
1211
import { buildCompilerInput, type ContractSources } from "$lib/models/solc";
1312
import Message from "$lib/wizard/components/shared/Message.svelte";
14-
import type { Artifact, DeployContractRequest } from "$lib/models/deploy";
13+
import type { Artifact, DeployContractRequest, UpdateDeploymentRequest } from "$lib/models/deploy";
1514
import { getNetworkLiteral } from "$lib/models/network";
15+
import { attempt } from "$lib/utils/attempt";
16+
import { getContractBytecode } from "$lib/utils/contracts";
17+
import { deployContract, switchToNetwork } from "$lib/ethereum";
18+
import type { ApprovalProcess as ApprovalProcessType, CreateApprovalProcessRequest } from "$lib/models/approval-process";
19+
import type { APIResponse } from "$lib/models/ui";
20+
import { isDeploymentEnvironment, isSameNetwork } from "$lib/utils/helpers";
21+
import ApprovalProcess from "$lib/wizard/components/ApprovalProcess.svelte";
22+
23+
interface DeploymentResult {
24+
deploymentId?: string;
25+
address: string;
26+
hash: string;
27+
sender?: string;
28+
}
1629
1730
let busy = $state(false);
1831
let successMessage = $state<string>("");
1932
let errorMessage = $state<string>("");
2033
let compilationResult = $state<{ output: Artifact['output'] }>();
21-
34+
let deploymentId = $state<string | undefined>(undefined);
2235
let deploymentArtifact = $derived.by(() => {
2336
if (!compilationResult || !globalState.contract?.target || !globalState.contract.source?.sources) return;
2437
@@ -28,6 +41,21 @@
2841
}
2942
});
3043
44+
let contractBytecode = $derived.by(() => {
45+
if (!globalState.contract?.target || !compilationResult) return;
46+
47+
const name = globalState.contract.target;
48+
const sources = compilationResult.output.contracts;
49+
50+
return getContractBytecode(name, name, sources);
51+
});
52+
53+
let constructorBytecode = $derived.by(() => {
54+
return "0x";
55+
});
56+
57+
let deploymentResult = $state<DeploymentResult | undefined>(undefined);
58+
3159
let currentStep = $state(0);
3260
function toggleStep(step: number) {
3361
currentStep = step;
@@ -62,15 +90,130 @@
6290
compilationResult = res.data;
6391
}
6492
93+
export async function handleInjectedProviderDeployment(bytecode: string) {
94+
// Switch network if needed
95+
const [, networkError] = await attempt(async () => switchToNetwork(globalState.form.network!));
96+
if (networkError) {
97+
throw new Error(`Error switching network: ${networkError.msg}`);
98+
}
99+
100+
const [result, error] = await attempt(async () => deployContract(bytecode));
101+
if (error) {
102+
throw new Error(`Error deploying contract: ${error.msg}`);
103+
}
104+
105+
if (!result) {
106+
throw new Error("Deployment result not found");
107+
}
108+
109+
displayMessage(`Contract deployed successfully, hash: ${result?.hash}`, "success");
110+
111+
return {
112+
address: result.address,
113+
hash: result.hash,
114+
sender: result.sender
115+
};
116+
}
117+
118+
function findDeploymentEnvironment(via?: string, network?: string) {
119+
if (!via || !network) return undefined;
120+
return globalState.approvalProcesses.find((ap) =>
121+
ap.network &&
122+
isDeploymentEnvironment(ap) &&
123+
isSameNetwork(ap.network, network) &&
124+
ap.via?.toLocaleLowerCase() === via.toLocaleLowerCase()
125+
);
126+
}
127+
128+
129+
async function getOrCreateApprovalProcess(): Promise<ApprovalProcessType | undefined> {
130+
const ap = globalState.form.approvalProcessToCreate;
131+
if (!ap || !ap.via || !ap.viaType) {
132+
displayMessage("Must select an approval process to create", "error");
133+
return;
134+
}
65135
66-
const deploy = async () => {
67136
if (!globalState.form.network) {
68-
displayMessage("No network selected", "error");
137+
displayMessage("Must select a network", "error");
69138
return;
70139
}
71140
72-
if (!globalState.form.approvalProcessSelected) {
73-
displayMessage("No approval process selected", "error");
141+
const existing = findDeploymentEnvironment(ap.via, ap.network);
142+
if (existing) {
143+
return existing;
144+
}
145+
146+
const apRequest: CreateApprovalProcessRequest = {
147+
name: `Deploy From Remix - ${ap.viaType}`,
148+
via: ap.via,
149+
viaType: ap.viaType,
150+
network: getNetworkLiteral(globalState.form.network),
151+
relayerId: ap.relayerId,
152+
component: ["deploy"],
153+
};
154+
const result: APIResponse<{ approvalProcess: ApprovalProcessType }> =
155+
await API.createApprovalProcess(apRequest);
156+
157+
if (!result.success) {
158+
displayMessage(`Approval process creation failed, error: ${JSON.stringify(result.error)}`, "error");
159+
return;
160+
}
161+
162+
displayMessage("Deployment Environment successfully created", "success");
163+
if (!result.data) return;
164+
165+
addAPToDropdown(result.data.approvalProcess)
166+
return result.data.approvalProcess;
167+
}
168+
169+
export async function createDefenderDeployment(request: DeployContractRequest) {
170+
const result: APIResponse<{ deployment: { deploymentId: string } }> =
171+
await API.createDeployment(request);
172+
173+
if (!result.success || !result.data) {
174+
throw new Error(`Contract deployment creation failed: ${JSON.stringify(result.error)}`);
175+
}
176+
177+
return result.data.deployment.deploymentId;
178+
}
179+
180+
export async function updateDeploymentStatus(
181+
deploymentId: string,
182+
address: string,
183+
hash: string
184+
) {
185+
const updateRequest: UpdateDeploymentRequest = {
186+
deploymentId,
187+
hash,
188+
address,
189+
};
190+
191+
const result = await API.updateDeployment(updateRequest);
192+
if (!result.success) {
193+
throw new Error(`Failed to update deployment status: ${JSON.stringify(result.error)}`);
194+
}
195+
}
196+
197+
// function listenForDeployment(deploymentId: string) {
198+
// // polls the deployment status every 10 seconds
199+
// const interval = 10000;
200+
// timeout = setInterval(async () => {
201+
// const result: APIResponse<{ address: string, hash: string }> = await API.getDeployment(deploymentId);
202+
// if (result.success && result.data?.address) {
203+
// deploymentResult = {
204+
// address: result.data.address,
205+
// hash: result.data.hash,
206+
// };
207+
// logDeploymentResult(deploymentResult);
208+
// clearInterval(timeout);
209+
// }
210+
// }, interval);
211+
// }
212+
213+
214+
const deploy = async () => {
215+
if (!globalState.form.network) {
216+
displayMessage("No network selected", "error");
74217
return;
75218
}
76219
@@ -84,24 +227,73 @@
84227
return;
85228
}
86229
230+
// contract deployment requires contract bytecode
231+
// and constructor bytecode to be concatenated.
232+
const bytecode = contractBytecode + constructorBytecode?.slice(2);
233+
234+
const shouldUseInjectedProvider = globalState.form.approvalType === "injected";
235+
if (shouldUseInjectedProvider) {
236+
const [result, error] = await attempt(async () =>
237+
handleInjectedProviderDeployment(bytecode),
238+
);
239+
if (error) {
240+
displayMessage(`Error deploying contract: ${error.msg}`, "error");
241+
return;
242+
}
243+
244+
deploymentResult = result;
245+
246+
// loads global state with EOA approval process to create
247+
globalState.form.approvalProcessToCreate = {
248+
viaType: "EOA",
249+
via: deploymentResult?.sender,
250+
network: getNetworkLiteral(globalState.form.network),
251+
};
252+
globalState.form.approvalProcessSelected = undefined;
253+
}
254+
255+
const approvalProcess = globalState.form.approvalProcessSelected ?? await getOrCreateApprovalProcess();
256+
if (!approvalProcess) {
257+
displayMessage("No Approval Process selected", "error");
258+
return;
259+
};
260+
87261
const deployRequest: DeployContractRequest = {
88-
artifactPayload: JSON.stringify(deploymentArtifact),
89262
network: getNetworkLiteral(globalState.form.network),
90-
// TODO: Implement approval process creation
91-
approvalProcessId: globalState.form.approvalProcessSelected.approvalProcessId,
263+
approvalProcessId: approvalProcess.approvalProcessId,
92264
contractName: globalState.contract!.target,
93265
contractPath: globalState.contract!.target,
94266
verifySourceCode: true,
95-
// TODO: Implement constructor arguments
267+
licenseType: 'MIT',
268+
artifactPayload: JSON.stringify(deploymentArtifact),
269+
// TODO: Implement constructor arguments + salt
96270
constructorBytecode: '',
271+
salt: '',
97272
}
98273
99-
const res = await API.createDeployment(deployRequest);
100-
if (!res.success) {
101-
displayMessage(`Deployment failed: ${res.error}`, "error");
274+
const [newDeploymentId, deployError] = await attempt(async () => createDefenderDeployment(deployRequest));
275+
if (deployError || !newDeploymentId) {
276+
displayMessage(`Deployment failed to create: ${deployError?.msg}`, "error");
102277
return;
103278
}
104279
280+
if (shouldUseInjectedProvider && deploymentResult) {
281+
const [, updateError] = await attempt(async () => updateDeploymentStatus(
282+
newDeploymentId,
283+
deploymentResult!.address,
284+
deploymentResult!.hash
285+
));
286+
if (updateError) {
287+
displayMessage(`Error updating deployment status: ${updateError.msg}`, "error");
288+
return;
289+
}
290+
} else {
291+
// If we're not using an injected provider
292+
// we need to listen for the deployment to be finished.
293+
// listenForDeployment(newDeploymentId);
294+
}
295+
296+
deploymentId = newDeploymentId;
105297
displayMessage("Deployment successful", "success");
106298
};
107299
@@ -145,9 +337,6 @@
145337
<ApprovalProcess />
146338
</div>
147339
</div>
148-
{#if compilationResult}
149-
{JSON.stringify(compilationResult, null, 2)}
150-
{/if}
151340
</div>
152341
<div class="sticky px-4 flex flex-col gap-2">
153342
<Button disabled={!globalState.authenticated || busy} loading={busy} label="Deploy" onClick={compileAndDeploy} />

0 commit comments

Comments
 (0)