Skip to content
Snippets Groups Projects
Unverified Commit 84f1dfdf authored by Mateusz Tyszczak's avatar Mateusz Tyszczak :scroll:
Browse files

Allow account creation

parent 78a9fffa
No related branches found
No related tags found
No related merge requests found
Pipeline #117527 passed
<script setup lang="ts">
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { ref, onMounted } from 'vue';
import step1 from "@/assets/icons/wallets/metamask/step1.webp";
import step2 from "@/assets/icons/wallets/metamask/step2.webp";
......@@ -11,11 +13,25 @@ import { Combobox, ComboboxAnchor, ComboboxTrigger, ComboboxEmpty, ComboboxGroup
import { Check, Search } from 'lucide-vue-next';
import { Separator } from '@/components/ui/separator';
import PublicKey from '@/components/hive/PublicKey.vue';
import { Checkbox } from '@/components/ui/checkbox'
import { getWax } from '@/stores/wax.store';
import { AccountAuthorityUpdateOperation, type TRole } from "@hiveio/wax/vite";
const emit = defineEmits(["setaccount", "close"]);
const showUpdateAccountModal = ref(false);
const showCreateAccountModal = ref(false);
const updateAuthType: Record<TRole, boolean> = {
owner: true,
active: true,
posting: true,
memo: true
};
const close = () => {
showUpdateAccountModal.value = false;
showCreateAccountModal.value = false;
emit("close");
};
......@@ -24,6 +40,8 @@ const metamaskStore = useMetamaskStore();
const isLoading = ref(false);
const errorMsg = ref<string | null>(null);
const accountName = ref<string | null>(null);
const createAccountNameOperation = ref<string | null>(null);
const updateAccountNameOperation = ref<string | null>(null);
const accountsMatchingKeys = ref<string[] | null>(null);
const metamaskPublicKeys = ref<Array<{ role: string; publicKey: string }> | null>(null);
const isMetamaskConnected = ref<boolean>(false);
......@@ -110,8 +128,39 @@ onMounted(() => {
void connect(false);
});
const gotoSignTx = () => {
window.open(`${window.location.protocol}//${window.location.host}/sign/?tx=`, '_blank')!.focus();
const copyContent = (content: string) => {
navigator.clipboard.writeText(String(content));
};
const generateAccountUpdateTransaction = async(): Promise<string> => {
const wax = await getWax();
const tx = await wax.createTransaction();
const accountName = updateAccountNameOperation.value!.startsWith('@') ? updateAccountNameOperation.value!.slice(1) : updateAccountNameOperation.value!;
const op = await AccountAuthorityUpdateOperation.createFor(wax, accountName);
for(const key in updateAuthType) {
if (updateAuthType[key as TRole])
if (key === "memo")
op.role("memo").set(metamaskPublicKeys.value!.find(node => node.role === key)!.publicKey);
else
op.role(key as Exclude<TRole, "memo">).add(metamaskPublicKeys.value!.find(node => node.role === key)!.publicKey);
}
tx.pushOperation(op);
return tx.toApi();
};
const getAccountCreateSigningLink = async () => {
const accountName = createAccountNameOperation.value!.startsWith('@') ? createAccountNameOperation.value!.slice(1) : createAccountNameOperation.value!;
return `${window.location.protocol}//${window.location.host}/account/create?acc=${accountName}&posting=${
metamaskPublicKeys.value!.find(node => node.role === "posting")!.publicKey
}&active=${
metamaskPublicKeys.value!.find(node => node.role === "active")!.publicKey
}&owner=${
metamaskPublicKeys.value!.find(node => node.role === "owner")!.publicKey
}&memo=${
metamaskPublicKeys.value!.find(node => node.role === "memo")!.publicKey
}`;
};
const getSigningLink = async () => {
const tx = await generateAccountUpdateTransaction();
return `${window.location.protocol}//${window.location.host}/sign/transaction?data=${btoa(tx)}`;
};
const updateAccountName = (value: string | any) => {
......@@ -165,7 +214,54 @@ const updateAccountName = (value: string | any) => {
</div>
<div v-if="isMetamaskSnapInstalled">
<div v-if="accountsMatchingKeys">
<div v-if="accountsMatchingKeys.length === 0">
<div v-if="showUpdateAccountModal">
<p class="mb-4">Step 6: Fill in this form in order to create account update operation, replacing memo public key and adding posting, active and owner keys to your account:</p>
<div class="grid mb-2 w-full max-w-sm items-center gap-1.5">
<Label for="metamask_updateAuth_account">Account name</Label>
<Input v-model="updateAccountNameOperation as string" id="metamask_updateAuth_account" />
</div>
<div v-for="key in metamaskPublicKeys" :key="key.publicKey">
<div class="flex items-center p-1">
<Checkbox :id="`metamask_updateAuth_key-${key.role}`" :defaultValue="true" @update:modelValue="value => { updateAuthType[key.role as TRole] = value as boolean }" />
<label :for="`metamask_updateAuth_key-${key.role}`" class="pl-2 w-full flex items-center">
<span class="font-bold">{{ key.role[0].toUpperCase() }}{{ key.role.slice(1) }}</span>
<div class="mx-2 border flex-grow border-[hsl(var(--foreground))] opacity-[0.1]" />
<PublicKey :value="key.publicKey"/>
</label>
</div>
</div>
<div class="flex items-center flex-col">
<Button :disabled="isLoading" @click="getSigningLink().then(copyContent)" variant="outline" size="lg" class="mt-4 px-8 py-4 border-[#FF5C16] border-[1px]">
<span class="text-md font-bold">Copy signing link</span>
</Button>
<Separator label="Or" class="mt-8" />
<div class="flex justify-center mt-4">
<Button :disabled="isLoading" @click="generateAccountUpdateTransaction().then(copyContent)" variant="outline" size="lg" class="px-8 opacity-[0.9] py-4 border-[#FF5C16] border-[1px]">
<span class="text-md font-bold">Copy entire transaction</span>
</Button>
</div>
</div>
</div>
<div v-else-if="showCreateAccountModal">
<p class="mb-4">Step 6: Fill in this form in order to create account create operation with requested metadata. Copy the signing link and send it to someone who already has an account:</p>
<div class="grid mb-2 w-full max-w-sm items-center gap-1.5">
<Label for="metamask_createAuth_account">New account name</Label>
<Input v-model="createAccountNameOperation as string" id="metamask_createAuth_account" />
</div>
<div v-for="key in metamaskPublicKeys" :key="key.publicKey">
<div class="flex items-center p-1">
<span class="font-bold">{{ key.role[0].toUpperCase() }}{{ key.role.slice(1) }}</span>
<div class="mx-2 border flex-grow border-[hsl(var(--foreground))] opacity-[0.1]" />
<PublicKey :value="key.publicKey"/>
</div>
</div>
<div class="flex items-center flex-col">
<Button :disabled="isLoading" @click="getAccountCreateSigningLink().then(copyContent)" variant="outline" size="lg" class="mt-4 px-8 py-4 border-[#FF5C16] border-[1px]">
<span class="text-md font-bold">Copy signing link</span>
</Button>
</div>
</div>
<div v-else-if="accountsMatchingKeys.length === 0">
<p class="mb-2">Step 5: Import <b>at least one</b> Metamask derived key into your Hive account and re-check for Hive Accounts matching those keys:</p>
<div v-for="key in metamaskPublicKeys" :key="key.publicKey">
<div class="flex items-center p-1">
......@@ -181,12 +277,12 @@ const updateAccountName = (value: string | any) => {
</div>
<Separator label="Or" class="mt-8" />
<div class="flex justify-center mt-4">
<Button :disabled="isLoading" @click="gotoSignTx" variant="outline" size="lg" class="px-8 opacity-[0.9] py-4 border-[#FF5C16] border-[1px]">
<Button :disabled="isLoading" @click="showUpdateAccountModal = true" variant="outline" size="lg" class="px-8 opacity-[0.9] py-4 border-[#FF5C16] border-[1px]">
<span class="text-md font-bold">Update account authority</span>
</Button>
</div>
<div class="flex justify-center mt-4">
<Button :disabled="isLoading" @click="gotoSignTx" variant="outline" size="lg" class="px-8 opacity-[0.9] py-4 border-[#FF5C16] border-[1px]">
<Button :disabled="isLoading" @click="showCreateAccountModal = true" variant="outline" size="lg" class="px-8 opacity-[0.9] py-4 border-[#FF5C16] border-[1px]">
<span class="text-md font-bold">Request account creation</span>
</Button>
</div>
......
<script setup lang="ts">
import type { CheckboxRootEmits, CheckboxRootProps } from 'reka-ui'
import { cn } from '@/lib/utils'
import { Check } from 'lucide-vue-next'
import { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from 'reka-ui'
import { computed, type HTMLAttributes } from 'vue'
const props = defineProps<CheckboxRootProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<CheckboxRootEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<CheckboxRoot
v-bind="forwarded"
:class="
cn('peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
props.class)"
>
<CheckboxIndicator class="flex h-full w-full items-center justify-center text-current">
<slot>
<Check class="h-4 w-4" />
</slot>
</CheckboxIndicator>
</CheckboxRoot>
</template>
export { default as Checkbox } from './Checkbox.vue'
......@@ -32,10 +32,12 @@ const copyBtn = (event: MouseEvent) => {
<Primitive
:as="as"
:as-child="asChild"
:class="cn(buttonVariants(), props.class, 'px-2')"
:class="cn(buttonVariants(), 'px-2', props.class)"
@click="copyBtn"
:data-copy="props.value"
>
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill: hsl(var(--foreground))" :d="mdiContentCopy"/></svg>
<slot>
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill: hsl(var(--foreground))" :d="mdiContentCopy"/></svg>
</slot>
</Primitive>
</template>
......@@ -3,7 +3,69 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
import { mdiAccountPlusOutline } from '@mdi/js';
import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
import { Input } from '@/components/ui/input';
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { useSettingsStore } from '@/stores/settings.store';
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { getWax } from '@/stores/wax.store';
import { useWalletStore } from '@/stores/wallet.store';
const settings = useSettingsStore();
const creator = ref<string>('');
const accountName = ref<string>('');
const memoKey = ref<string>('');
const postingKey = ref<string>('');
const activeKey = ref<string>('');
const ownerKey = ref<string>('');
const postingMetadata = ref<string>('{}');
const router = useRouter();
const wallet = useWalletStore();
onMounted(() => {
creator.value = settings.account ? `@${settings.account}` : '';
accountName.value = router.currentRoute.value.query.acc as string ?? '';
memoKey.value = router.currentRoute.value.query.memo as string ?? '';
postingKey.value = router.currentRoute.value.query.posting as string ?? '';
activeKey.value = router.currentRoute.value.query.active as string ?? '';
ownerKey.value = router.currentRoute.value.query.owner as string ?? '';
});
const createAccount = async() => {
const wax = await getWax();
const tx = await wax.createTransaction();
const { median_props: { account_creation_fee } } = await wax.api.database_api.get_witness_schedule({});
tx.pushOperation({
account_create: {
creator: settings.account!,
new_account_name: accountName.value,
memo_key: memoKey.value,
owner: {
weight_threshold: 1,
key_auths: {[ownerKey.value]: 1},
account_auths: {}
},
active: {
weight_threshold: 1,
key_auths: {[activeKey.value]: 1},
account_auths: {}
},
posting: {
weight_threshold: 1,
key_auths: {[postingKey.value]: 1},
account_auths: {}
},
json_metadata: postingMetadata.value,
fee: account_creation_fee
}
});
const signature = await wallet.wallet!.signTransaction(tx, "active");
tx.sign(signature);
await wax.broadcast(tx);
}
</script>
<template>
......@@ -16,14 +78,36 @@ import { Input } from '@/components/ui/input';
<CardDescription class="mr-8">Use this module to process account creation request sent by other users</CardDescription>
</CardHeader>
<CardContent>
<div class="my-4">
<Input placeholder="Account username" class="my-2" disabled/>
<Input placeholder="Memo key" class="my-2" disabled/>
<Input placeholder="Posting key" class="my-2" disabled/>
<Input placeholder="Active key" class="my-2" disabled/>
<Input placeholder="Owner key" class="my-2" disabled/>
<Textarea placeholder="Posting metadata" class="my-2" disabled/>
<Button class="my-2">Create account</Button>
<div class="my-4 space-y-2">
<div class="grid w-full max-w-sm items-center">
<Label for="createAccount_creator">Account name</Label>
<Input id="createAccount_creator" v-model="creator" class="my-2" disabled />
</div>
<div class="grid w-full max-w-sm items-center">
<Label for="createAccount_accountName">Account Name</Label>
<Input id="createAccount_accountName" v-model="accountName" class="my-2" />
</div>
<div class="grid w-full max-w-sm items-center">
<Label for="createAccount_memoKey">Memo Key</Label>
<Input id="createAccount_memoKey" v-model="memoKey" class="my-2" />
</div>
<div class="grid w-full max-w-sm items-center">
<Label for="createAccount_postingKey">Posting Key</Label>
<Input id="createAccount_postingKey" v-model="postingKey" class="my-2" />
</div>
<div class="grid w-full max-w-sm items-center">
<Label for="createAccount_activeKey">Active Key</Label>
<Input id="createAccount_activeKey" v-model="activeKey" class="my-2" />
</div>
<div class="grid w-full max-w-sm items-center">
<Label for="createAccount_ownerKey">Owner Key</Label>
<Input id="createAccount_ownerKey" v-model="ownerKey" class="my-2" />
</div>
<div class="grid w-full max-w-sm items-center">
<Label for="createAccount_postingMetadata">Posting Metadata</Label>
<Textarea id="createAccount_postingMetadata" v-model="postingMetadata" class="my-2" />
</div>
<Button class="my-2" @click="createAccount">Create account</Button>
</div>
</CardContent>
</Card>
......
import { createHiveChain, type IHiveChainInterface } from "@hiveio/wax/vite";
import { createHiveChain, type TWaxExtended, type asset } from "@hiveio/wax/vite";
let chain: IHiveChainInterface;
export interface WaxApi {
database_api: {
get_witness_schedule: {
params: {};
result: {
median_props: {
account_creation_fee: asset;
};
};
};
};
};
let chain: TWaxExtended<WaxApi>;
export const getWax = async(): Promise<IHiveChainInterface> => {
export const getWax = async() => {
if (!chain)
chain = await createHiveChain();
chain = (await createHiveChain()).extend<WaxApi>();
return chain;
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment