Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for multisig accounts #384

Merged
merged 34 commits into from
Feb 24, 2025
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
1029e2f
msig prototype
aaroncox Feb 23, 2025
d46b7f2
Added permission when matching current session
aaroncox Feb 23, 2025
d59678c
Redirect to new account when logging out of current
aaroncox Feb 23, 2025
92dfc65
Show multisig indicator on account
aaroncox Feb 23, 2025
c73b731
Reworked logout logic for better automatic switching
aaroncox Feb 23, 2025
9afd44a
Moved MultiSig plugin to own file
aaroncox Feb 23, 2025
7030041
Moved all Wharf plugins to own file
aaroncox Feb 23, 2025
c9b63ae
Removed unused (?) effect
aaroncox Feb 23, 2025
2af3530
Linting
aaroncox Feb 23, 2025
e50358c
Reworked resources
aaroncox Feb 23, 2025
3aa1ff3
Removed debug link + added more text
aaroncox Feb 23, 2025
a5ce003
Prevent multisig wallet from being a default login option
aaroncox Feb 23, 2025
18a3961
Added account data update timer when in debug mode
aaroncox Feb 23, 2025
0622519
Change default update to 5 seconds
aaroncox Feb 23, 2025
a3a9b3c
Renamed manifest so it won't be picked up
aaroncox Feb 24, 2025
0f0fb0c
Removed reference to manifest
aaroncox Feb 24, 2025
e7926de
Better activity filtering
aaroncox Feb 24, 2025
6adc1c9
Added support for time.eosn contract + msig preference for earliest e…
aaroncox Feb 24, 2025
b758141
Created datetime input
aaroncox Feb 24, 2025
60c3295
Made selected optional
aaroncox Feb 24, 2025
516a569
Removed excess debugging information
aaroncox Feb 24, 2025
0cb57ca
Store result for display
aaroncox Feb 24, 2025
9d64735
Indicator of no authorities
aaroncox Feb 24, 2025
e7704df
Linting
aaroncox Feb 24, 2025
8f58f89
Fixed issue with no rex balance breaking the staking page
aaroncox Feb 24, 2025
6cfe7e9
Went through and updated to wrapped `goto` call
aaroncox Feb 24, 2025
cb69f6f
Update src/routes/[network]/settings/+page.svelte
aaroncox Feb 24, 2025
e692369
Localized account switcher content
aaroncox Feb 24, 2025
5540a00
Linting
aaroncox Feb 24, 2025
df68876
Undo rename
aaroncox Feb 24, 2025
41cebc8
Deleted unused page
aaroncox Feb 24, 2025
7b1ee56
Reverted optional selected option, and force default in settings
aaroncox Feb 24, 2025
dd5dd02
Changing h2 to h4 to reduce emphasis on authorities
aaroncox Feb 24, 2025
39af9c9
Catch when network is not defined and return 404
aaroncox Feb 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ $(CONTRACTS)/delphioracle.ts:
$(CONTRACTS)/unicove.ts:
bunx @wharfkit/cli generate -u $(CONTRACTS_API) -f $(CONTRACTS)/unicove.ts unicove.gm

codegen: $(CONTRACTS)/system.ts $(CONTRACTS)/token.ts $(CONTRACTS)/msig.ts $(CONTRACTS)/delphioracle.ts $(CONTRACTS)/unicove.ts
$(CONTRACTS)/eosntime.ts:
bunx @wharfkit/cli generate -u $(CONTRACTS_API) -f $(CONTRACTS)/eosntime.ts time.eosn

codegen: $(CONTRACTS)/system.ts $(CONTRACTS)/token.ts $(CONTRACTS)/msig.ts $(CONTRACTS)/delphioracle.ts $(CONTRACTS)/unicove.ts $(CONTRACTS)/eosntime.ts
mkdir -p $(CONTRACTS)

.PHONY: clean
Expand Down
2 changes: 1 addition & 1 deletion messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@
"navigation_balances": "Balances",
"navigation_overview": "Overview",
"navigation_permissions": "Permissions",
"navigation_proposals": "Proposals",
"navigation_proposals": "Proposals ({number})",
"navigation_resources": "Resources",
"navigation_staked": "Staked",
"navigation_votes": "Votes",
Expand Down
2 changes: 1 addition & 1 deletion messages/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@
"navigation_balances": "잔액",
"navigation_overview": "개요",
"navigation_permissions": "권한",
"navigation_proposals": "제안",
"navigation_proposals": "제안 ({number})",
"navigation_resources": "자원",
"navigation_staked": "지분을 걸다",
"navigation_votes": "투표",
Expand Down
2 changes: 1 addition & 1 deletion messages/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@
"navigation_balances": "余额",
"navigation_overview": "概览",
"navigation_permissions": "权限",
"navigation_proposals": "提案",
"navigation_proposals": "提案 ({number})",
"navigation_resources": "资源",
"navigation_staked": "质押中",
"navigation_votes": "投票",
Expand Down
3 changes: 3 additions & 0 deletions scripts/env/default/chains.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"features": {
"delphioracle": true,
"directfunding": true,
"eosntime": true,
"giftedram": false,
"lightapi": true,
"metamask": true,
Expand Down Expand Up @@ -42,6 +43,7 @@
"features": {
"delphioracle": false,
"directfunding": false,
"eosntime": false,
"giftedram": true,
"lightapi": false,
"metamask": true,
Expand Down Expand Up @@ -76,6 +78,7 @@
"features": {
"delphioracle": false,
"directfunding": false,
"eosntime": false,
"lightapi": false,
"metamask": false,
"powerup": true,
Expand Down
2 changes: 1 addition & 1 deletion src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<link rel="icon" type="image/svg+xml" href="/favicon.svg" sizes="any" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<meta name="apple-mobile-web-app-title" content="Unicove" />
<link rel="manifest" href="/site.webmanifest" />
<!-- <link rel="manifest" href="/site.webmanifest" /> -->
<style>
@font-face {
font-family: 'Inter';
Expand Down
60 changes: 36 additions & 24 deletions src/lib/components/accountswitch.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,20 @@
closeDrawer();
}

function removeSession(session?: Session | SerializedSession) {
async function removeSession(session?: Session | SerializedSession) {
if (session) {
context.wharf.logout(session);
await context.wharf.logout(session);
if (currentSession) {
redirect(currentSession.actor);
}
}
}

async function connectWallet(wallet: WalletPlugin) {
const options: LoginOptions = {
walletPlugin: wallet.id
};
if (wallet.id !== 'cleos') {
if (wallet.id !== 'cleos' && wallet.id !== 'wallet-plugin-multisig') {
options.chain = context.network.chain;
}
const session = await context.wharf.login(options);
Expand Down Expand Up @@ -232,7 +235,9 @@

<ul class="grid gap-2">
{#each chainSessions as session}
{@const isCurrent = currentSession?.actor.toString() === session.actor}
{@const isCurrent =
currentSession?.actor.equals(session.actor) &&
currentSession?.permission.equals(session.permission)}
<li class="grid grid-cols-[1fr_auto] gap-2">
<button
data-current={isCurrent}
Expand All @@ -252,9 +257,14 @@
{/if}
</div>

<span class="font-medium">
{session.actor}@{session.permission}
</span>
<div class="text-left font-medium">
<div>{session.actor}@{session.permission}</div>
{#if session.walletPlugin.id === 'wallet-plugin-multisig'}
<div class="text-xs">
↳ multisig using {session.walletPlugin.data.session.actor}
</div>
{/if}
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious what this looks like in context with layout, the arrow glyph, and the small text.

Probably should use a translation here too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Here's what it looks like. I've updated all the translations in the account switcher here: e692369

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might call for a slightly lighter-weight font in the second line, but otherwise looks good.

I'm always hesitant to use glyphs in the font vs an svg because they can become unpredicatable with different fonts, different browser settings, zoom levels, but more importantly: it's decorative, so it shouldn't be included in the text string (which will be translated at some point)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah totally understood. We probably want a "real" design for these elements, since as I worked on this I was just implementing my developer design to make something available.

I'm very open to change with how its displayed.

</button>
<button
onclick={() => removeSession(session)}
Expand Down Expand Up @@ -292,23 +302,25 @@
{#if context.wharf.sessionKit}
<ul class="grid grid-cols-[auto_1fr_auto]">
{#each context.wharf.sessionKit?.walletPlugins as wallet}
<li class="table-row-background table-row-border col-span-full grid grid-cols-subgrid">
<button
class="col-span-full grid grid-cols-subgrid gap-4 px-2 py-4 font-semibold text-white"
onclick={() => connectWallet(wallet)}
>
{#if wallet.metadata.logo}
<img
class="size-6"
src={wallet.metadata.logo.toString()}
alt={wallet.metadata.name}
/>
{:else}
<Wallet class="size-6" />
{/if}
<span class="text-left">{wallet.metadata.name}</span>
</button>
</li>
{#if wallet.id !== 'wallet-plugin-multisig'}
<li class="table-row-background table-row-border col-span-full grid grid-cols-subgrid">
<button
class="col-span-full grid grid-cols-subgrid gap-4 px-2 py-4 font-semibold text-white"
onclick={() => connectWallet(wallet)}
>
{#if wallet.metadata.logo}
<img
class="size-6"
src={wallet.metadata.logo.toString()}
alt={wallet.metadata.name}
/>
{:else}
<Wallet class="size-6" />
{/if}
<span class="text-left">{wallet.metadata.name}</span>
</button>
</li>
{/if}
{/each}
</ul>
{/if}
Expand Down
27 changes: 27 additions & 0 deletions src/lib/components/elements/link.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script lang="ts">
import { cn } from '$lib/utils';
import { type Snippet } from 'svelte';
import type { HTMLAnchorAttributes } from 'svelte/elements';

interface Props extends HTMLAnchorAttributes {
href: string;
children?: Snippet;
text?: string;
}

let { href, text = '', children, ...props }: Props = $props();
</script>

<a
{href}
class={cn(
'inline-flex items-center gap-2 text-skyBlue-500 hover:text-skyBlue-400 focus-visible:outline focus-visible:outline-solar-500 ',
props.class
)}
>
{#if children}
{@render children()}
{:else}
{text}
{/if}
</a>
2 changes: 1 addition & 1 deletion src/lib/components/elements/resourceCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@

<p class="*:block">
<span class="font-semibold text-white">
<NumberFormat number={props.value} />
<NumberFormat number={props.value.dividing(1000)} />
{unit}
</span>
<span>{m.common_available()}</span>
Expand Down
84 changes: 84 additions & 0 deletions src/lib/components/input/datetime.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<script lang="ts">
import { cn } from '$lib/utils/style';
import type { Snippet } from 'svelte';
import type { HTMLInputAttributes } from 'svelte/elements';
import Code from '../code.svelte';

interface DatetimeInputProps extends HTMLInputAttributes {
date?: Date;
value?: string;
min?: string;
children?: Snippet;
debug?: boolean;
}

var now = new Date();
now.setMinutes(now.getMinutes() - now.getTimezoneOffset());

function dateToInputString(date: Date) {
return date.toISOString().slice(0, 16);
}

let {
date = $bindable(),
value = $bindable(),
min = dateToInputString(now),
class: className,
debug = false,
...props
}: DatetimeInputProps = $props();

value = undefined;
if (date) {
const utc = new Date(date);
utc.setMinutes(utc.getMinutes() - utc.getTimezoneOffset());
value = dateToInputString(utc);
}

/** Set the input value from a parent */
export function set(date: Date | undefined) {
if (!date) {
value = undefined;
} else {
value = dateToInputString(date);
}
}

$effect(() => {
if (value) {
date = new Date(`${value}:00.000`);
} else {
date = undefined;
}
});
</script>

<div
class={cn(
'relative flex h-12 gap-2 rounded-lg border-2 border-mineShaft-600 px-4 *:content-center focus-within:border-skyBlue-500 focus-within:ring focus-within:ring-1 focus-within:ring-inset focus-within:ring-skyBlue-500',
className
)}
>
<input
class="placeholder:text-muted w-full rounded-lg bg-transparent font-medium focus:outline-none"
type="datetime-local"
{min}
bind:value
{...props}
/>
<div class="text-muted select-none">
{@render props.children?.()}
</div>
</div>

{#if debug}
<div class="mt-4">
<h3>Component State</h3>
<!-- prettier-ignore -->
<Code>
date: {date}
value: {value}
min: {min}
</Code>
</div>
{/if}
2 changes: 1 addition & 1 deletion src/lib/components/select/select.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

interface Props {
options: ExtendedSelectOption[];
selected: ExtendedSelectOption;
selected?: ExtendedSelectOption;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC this was required in the melt-ui builder so I made it required in the interface

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll revert this change and force a default option on the settings page then.

The select element with a default option ands up triggering the effects on the page, setting the value to whatever the select believes the default option should be. This could override defaults set elsewhere.

So there's some funk in here at the moment we should try to figure out how to address.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, we should address the funk and see if we can make selected optional. It does make sense to have the option to create a dropdown with nothing selected and no default.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thanks!

onSelectedChange?: ChangeFn<ExtendedSelectOption | undefined>;
variant?: SelectOptionVariant;
id: string;
Expand Down
49 changes: 43 additions & 6 deletions src/lib/components/transact/summary.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
<script lang="ts">
import { transactions } from '$lib/wharf/transact.svelte';
import type { Checksum256 } from '@wharfkit/antelope';
import Transaction from '../elements/transaction.svelte';
import { Serializer, type Checksum256 } from '@wharfkit/antelope';
import Transaction from '$lib/components/elements/transaction.svelte';
import CircleCheckBig from 'lucide-svelte/icons/circle-check-big';
import ClipboardPen from 'lucide-svelte/icons/clipboard-pen';
import * as m from '$lib/paraglide/messages';
import type { UnicoveContext } from '$lib/state/client.svelte';
import { getContext } from 'svelte';

import { Types as MsigTypes } from '$lib/wharf/contracts/msig';
import Button from '$lib/components/button/button.svelte';

const context = getContext<UnicoveContext>('state');

interface Props {
transactionId?: Checksum256 | string;
Expand All @@ -15,15 +23,44 @@
const transaction = $derived(
transactions.find((t) => t.transaction?.id.equals(String(transactionId)))
);

const proposals = $derived(
transaction?.transaction?.actions
.filter((a) => a.name.equals('propose') && a.account.equals('eosio.msig'))
.map((p) => {
return Serializer.decode({
data: p.data,
type: MsigTypes.propose
});
})
);
</script>

<div class="space-y-6 rounded-lg" class:hidden>
{#if transaction}
<div class="flex flex-col items-center gap-6">
<picture class="size-24">
<CircleCheckBig class="size-full text-green-300" />
</picture>
<h2 class="h3">{m.common_transaction_complete()}</h2>
{#if proposals && proposals.length}
<picture class="size-24">
<ClipboardPen class="size-full text-green-300" />
</picture>
<h3 class="h3">Multi-Sig Proposal Created</h3>
<p class="text-center">
The multi-sig proposal for this transaction has been created and now needs to be approved.
View the proposal below and share it with the parties who need to sign.
Comment on lines +48 to +49
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be picky about translations at this point?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably should get better about this, and have someone in charge of that entire pipeline. Considering we're not 100% translated right now though, I don't know if it should be a barrier?

</p>
{#each proposals as proposal}
<Button href="/{context.network}/msig/{proposal.proposer}/{proposal.proposal_name}">
View Proposal ({proposal.proposer}/{proposal.proposal_name})
</Button>
{/each}
{:else}
<picture class="size-24">
<CircleCheckBig class="size-full text-green-300" />
</picture>
<h3 class="h3">
{m.common_transaction_complete()}
</h3>
{/if}
</div>
<!-- <h3 class="h3">{transaction.status}</h3> -->
<table class="table-styles">
Expand Down
9 changes: 8 additions & 1 deletion src/lib/state/client/account.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export class AccountState {
public delegations = $derived(getDelegations(this.sources));

public resources = $derived.by(() => getResources(this.sources, this.network));
public rex = $derived(SystemContract.Types.rex_balance.from(this.sources.rexbal));
public rex = $derived.by(() => getRex(this.sources));
public permissions = $derived(API.v1.AccountObject.from(this.sources.get_account).permissions);
public proposals = $derived.by(() => this.sources.proposals);
public refundRequest = $derived.by(() => this.sources.refund_request);
Expand Down Expand Up @@ -150,6 +150,13 @@ export class AccountState {
}
}

export function getRex(sources: AccountDataSources) {
if (!sources.rexbal) {
return defaultAccountDataSources.rexbal;
}
return SystemContract.Types.rex_balance.from(sources.rexbal);
}

export function getResources(
sources: AccountDataSources,
network?: NetworkState
Expand Down
Loading