Button to transfer org ownership

This commit is contained in:
Alexis Sellier
2021-05-25 19:02:40 +02:00
parent baee4cea9f
commit 19a85b7954
7 changed files with 186 additions and 11 deletions
+1
View File
@@ -14,6 +14,7 @@
import Modal from '@app/Modal.svelte';
function handleKeydown(event: KeyboardEvent) {
// TODO: Fix this when there's a modal.
if (event.key === 'Enter') {
(document.querySelector('button.primary') as HTMLElement).click();
}
+1
View File
@@ -10,6 +10,7 @@
margin: auto 0;
width: 70px;
text-align: center;
cursor: wait;
}
.spinner.center {
margin: auto auto;
+9 -2
View File
@@ -2,6 +2,7 @@
export let floating = false;
export let error = false;
export let subtle = false;
export let small = false;
</script>
<style>
@@ -57,6 +58,9 @@
}
.modal-subtitle {
color: var(--color-secondary);
max-width: 90%;
margin: 0 auto;
line-height: 1.5;
}
.modal-body {
overflow-x: hidden;
@@ -68,6 +72,9 @@
margin-top: 2rem;
text-align: center;
}
.modal-small .modal-subtitle {
color: var(--color-foreground);
}
</style>
{#if floating}
@@ -75,14 +82,14 @@
{/if}
<div class:modal-floating={floating}>
<div class="modal" class:error class:modal-subtle={subtle}>
<div class="modal" class:error class:modal-subtle={subtle} class:modal-small={small}>
<div class="modal-title">
<slot name="title"></slot>
</div>
<div class="modal-subtitle">
<slot name="subtitle"></slot>
</div>
{#if $$slots.body}
{#if $$slots.body && !small}
<div class="modal-body">
<slot name="body"></slot>
</div>
+12
View File
@@ -13,6 +13,7 @@ const orgFactoryAbi = [
const orgAbi = [
"function owner() view returns (address)",
"function setOwner(address)",
"function setName(string, address) returns (bytes32)",
];
@@ -43,6 +44,17 @@ export class Org {
{ gasLimit: 200_000 });
}
async setOwner(address: string, config: Config): Promise<TransactionResponse> {
assert(config.signer);
const org = new ethers.Contract(
this.address,
orgAbi,
config.signer
);
return org.setOwner(address);
}
static fromReceipt(receipt: ContractReceipt): Org | null {
let event = receipt.events?.find(e => e.event === 'OrgCreated');
+133
View File
@@ -0,0 +1,133 @@
<script lang="typescript">
import { onMount, createEventDispatcher } from 'svelte';
import Modal from '@app/Modal.svelte';
import type { Config } from '@app/config';
import { formatAddress } from '@app/utils';
import Loading from '@app/Loading.svelte';
import { assert } from '@app/error';
import * as utils from '@app/utils';
import type { Org } from './Org';
const dispatch = createEventDispatcher();
export let org: Org;
export let config: Config;
enum State {
Idle,
Signing,
Pending,
Success,
Failed,
}
let newOwner: string | null = null;
let state = State.Idle;
let error: string | null = null;
let input: HTMLInputElement | null = null;
onMount(() => {
input && input.focus();
});
const resetForm = () => {
state = State.Idle;
};
const onSubmit = async () => {
assert(newOwner);
if (! utils.isAddress(newOwner)) {
state = State.Failed;
error = `"${newOwner}" is not a valid Ethereum address.`;
return;
}
state = State.Signing;
try {
let tx = await org.setOwner(newOwner, config);
state = State.Pending;
await tx.wait();
state = State.Success;
} catch (e) {
console.error(e);
state = State.Failed;
error = e.message;
}
};
</script>
{#if state === State.Success}
<Modal floating small>
<div slot="title">
✅
</div>
<div slot="subtitle">
The ownership of <strong>{formatAddress(org.address)}</strong> was
successfully transfered to <strong>{newOwner}</strong>.
</div>
<div slot="actions">
<button class="small" on:click={() => dispatch('close')}>
Done
</button>
</div>
</Modal>
{:else}
<Modal floating error={state == State.Failed} small={state == State.Failed}>
<div slot="title">
🔑
<div>Transfer ownership</div>
</div>
<div slot="subtitle">
{#if state == State.Signing}
Please confirm the transaction in your wallet.
{:else if state == State.Pending}
Transaction is being processed by the network...
{:else if state == State.Idle}
Transfer the ownership of Org <strong>{formatAddress(org.address)}</strong> to a new address.
{:else if state == State.Failed}
<div class="error">
{error}
</div>
{/if}
</div>
<div slot="body">
{#if state == State.Idle}
<input type="text" size="40" disabled={state !== State.Idle} bind:this={input} bind:value={newOwner} />
{:else if state == State.Pending || state == State.Signing}
<Loading small center />
{:else if state == State.Failed}
<!-- ... -->
{/if}
</div>
<div slot="actions">
{#if state == State.Signing}
<button class="small" on:click={() => dispatch('close')}>
Cancel
</button>
{:else if state == State.Pending}
<button class="small" on:click={() => dispatch('close')}>
Close
</button>
{:else if state == State.Failed}
<button class="small" on:click={resetForm}>
Back
</button>
{:else}
<button class="primary" on:click={onSubmit} disabled={!newOwner || state !== State.Idle}>
Submit
</button>
<button class="text" on:click={() => dispatch('close')}>
Cancel
</button>
{/if}
</div>
</Modal>
{/if}
+29 -8
View File
@@ -7,7 +7,6 @@
import type { Registration } from '@app/base/registrations/registrar';
import { getRegistration } from '@app/base/registrations/registrar';
import { parseEnsLabel, explorerLink } from '@app/utils';
import { Org } from './Org';
import { session } from '@app/session';
import Loading from '@app/Loading.svelte';
import Modal from '@app/Modal.svelte';
@@ -16,6 +15,9 @@
import SetName from '@app/ens/SetName.svelte';
import * as utils from '@app/utils';
import { Org } from './Org';
import TransferOwnership from './TransferOwnership.svelte';
export let address: string;
export let config: Config;
@@ -34,6 +36,11 @@
setNameForm = SetName;
};
let transferOwnerForm: typeof SvelteComponent | null = null;
const transferOwnership = () => {
transferOwnerForm = TransferOwnership;
};
$: label = name && parseEnsLabel(name, config);
$: isOwner = (org: Org): boolean => {
return org.safe === ($session && $session.address);
@@ -61,8 +68,8 @@
}
.fields {
display: grid;
grid-template-columns: 1fr 8fr;
grid-gap: 1rem;
grid-template-columns: 1fr 4fr 2fr;
grid-gap: 1rem 2rem;
}
.fields > div {
justify-self: start;
@@ -119,8 +126,18 @@
</header>
<div class="fields">
<div class="label">Address</div><div>{org.address}</div>
<!-- Address -->
<div class="label">Address</div><div>{org.address}</div><div></div>
<!-- Owner -->
<div class="label">Owner</div><div>{org.safe}</div>
<div>
{#if isOwner(org)}
<button class="tiny secondary" on:click={transferOwnership}>
Transfer
</button>
{/if}
</div>
<!-- Name -->
<div class="label">Name</div>
<div>
{#await org.lookupAddress(config)}
@@ -128,15 +145,18 @@
{:then name}
{#if name}
<Link to={`/registrations/${label}`}>{name}</Link>
{:else if isOwner(org)}
<button class="tiny primary" on:click={setName}>
Set
</button>
{:else}
<span class="subtle">Not set</span>
{/if}
{/await}
</div>
<div>
{#if isOwner(org)}
<button class="tiny primary" on:click={setName}>
Set
</button>
{/if}
</div>
</div>
</main>
{:else}
@@ -160,6 +180,7 @@
</Modal>
{/if}
<svelte:component this={setNameForm} {org} {config} on:close={() => setNameForm = null} />
<svelte:component this={transferOwnerForm} {org} {config} on:close={() => transferOwnerForm = null} />
{:catch err}
<Error error={err} />
{/await}
+1 -1
View File
@@ -78,7 +78,7 @@
<div slot="subtitle">
{#if mismatchError}
<div class="rror">
<div class="error">
The name <strong>{name}.{config.registrar.domain}</strong> does not
resolve to <strong>{formatAddress(org.address)}</strong>. Please update
The ENS record for {name}.{config.registrar.domain} to