From 999c6b01ccf6ad7706af7c67492921940896dd37 Mon Sep 17 00:00:00 2001 From: Aaron Cox Date: Sat, 22 Feb 2025 14:02:13 -0800 Subject: [PATCH 01/36] Moved unstaking above calculator --- src/routes/[network]/(account)/staking/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/[network]/(account)/staking/+page.svelte b/src/routes/[network]/(account)/staking/+page.svelte index 148261fb..89b87781 100644 --- a/src/routes/[network]/(account)/staking/+page.svelte +++ b/src/routes/[network]/(account)/staking/+page.svelte @@ -141,6 +141,6 @@ - + From cb1136e81f115626e05d431bec74e28b123ef06c Mon Sep 17 00:00:00 2001 From: Aaron Cox Date: Sat, 22 Feb 2025 15:08:49 -0800 Subject: [PATCH 02/36] Use new number component for resources --- src/lib/components/elements/cpunetresource.svelte | 12 ++++++------ src/lib/components/elements/ramresource.svelte | 10 +++++----- src/lib/components/elements/resourceCard.svelte | 10 ++++++++-- .../[network]/(explorer)/account/[name]/+page.svelte | 4 ++-- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/lib/components/elements/cpunetresource.svelte b/src/lib/components/elements/cpunetresource.svelte index 6646e271..a60b762f 100644 --- a/src/lib/components/elements/cpunetresource.svelte +++ b/src/lib/components/elements/cpunetresource.svelte @@ -1,20 +1,20 @@
- +
- +
diff --git a/src/lib/components/elements/ramresource.svelte b/src/lib/components/elements/ramresource.svelte index d346a89c..93f9b789 100644 --- a/src/lib/components/elements/ramresource.svelte +++ b/src/lib/components/elements/ramresource.svelte @@ -1,17 +1,17 @@
- +
diff --git a/src/lib/components/elements/resourceCard.svelte b/src/lib/components/elements/resourceCard.svelte index b48c0ddb..d1dead0b 100644 --- a/src/lib/components/elements/resourceCard.svelte +++ b/src/lib/components/elements/resourceCard.svelte @@ -1,13 +1,16 @@ + + + {#if children} + {@render children()} + {:else} + {text} + {/if} + diff --git a/src/lib/components/transact/summary.svelte b/src/lib/components/transact/summary.svelte index f7e562cb..f27f2c47 100644 --- a/src/lib/components/transact/summary.svelte +++ b/src/lib/components/transact/summary.svelte @@ -1,9 +1,18 @@
{#if transaction}
- - - -

{m.common_transaction_complete()}

+ {#if proposals && proposals.length} + + + +

Multi-Sig Proposal Created

+

+ The multi-sig proposal for this transaction has been created and now needs to be approved. +

+ /en/jungle4/msig/oracle1.gm/t1p5cc1bx5bm + {#each proposals as proposal} + + {/each} + {:else} + + + +

+ {m.common_transaction_complete()} +

+ {/if}
diff --git a/src/lib/state/client/wharf.svelte.ts b/src/lib/state/client/wharf.svelte.ts index 8b3798aa..e6c13bc1 100644 --- a/src/lib/state/client/wharf.svelte.ts +++ b/src/lib/state/client/wharf.svelte.ts @@ -1,6 +1,15 @@ -import { ChainDefinition } from '@wharfkit/common'; +import { cancelable, ChainDefinition, type Cancelable } from '@wharfkit/common'; import ContractKit, { type ActionDataType } from '@wharfkit/contract'; -import { type NameType, Name, Serializer, Transaction } from '@wharfkit/antelope'; +import { + type NameType, + type PrivateKeyType, + Checksum256, + Name, + PermissionLevel, + PrivateKey, + Serializer, + Transaction +} from '@wharfkit/antelope'; import { type AccountCreationPlugin, type CreateAccountOptions, @@ -12,8 +21,16 @@ import { type TransactPlugin, type TransactResult, type WalletPlugin, + type WalletPluginConfig, + type WalletPluginLoginResponse, + type WalletPluginSignResponse, + AbstractWalletPlugin, + LoginContext, + ResolvedSigningRequest, Session, - SessionKit + SessionKit, + TransactContext, + WalletPluginMetadata } from '@wharfkit/session'; import WebRenderer from '@wharfkit/web-renderer'; @@ -39,12 +56,123 @@ import { chainMapper, chains, getChainDefinitionFromParams } from '$lib/wharf/ch import type { SettingsState } from '$lib/state/settings.svelte'; import type { NetworkState } from '$lib/state/network.svelte'; +import { Contract as MsigContract } from '$lib/wharf/contracts/msig'; +import { generateRandomName } from '$lib/utils/random'; + +export class WalletPluginMultiSig extends AbstractWalletPlugin implements WalletPlugin { + public id = 'wallet-plugin-multisig'; + readonly config: WalletPluginConfig = { + requiresChainSelect: true, + requiresPermissionEntry: true, + requiresPermissionSelect: true + }; + readonly metadata: WalletPluginMetadata = WalletPluginMetadata.from({ + name: 'MultiSig Proposer', + description: '' + }); + constructor() { + super(); + // const privateKey = PrivateKey.from(privateKeyData) + // this.data.privateKey = privateKey + // this.metadata.publicKey = String(privateKey.toPublic()) + // this.metadata.description = `An unsecured wallet that can sign for authorities using the ${ + // String(this.data.publicKey).substring(0, 11) + + // '...' + + // String(this.data.publicKey).substring( + // String(this.data.publicKey).length - 4, + // String(this.data.publicKey).length + // ) + // } public key.` + } + login(context: LoginContext): Cancelable { + let chain: Checksum256; + // Persist the parent session + this.data.session = context.arbitrary.session; + if (context.chain) { + chain = context.chain.id; + } else { + chain = context.chains[0].id; + } + return cancelable( + new Promise((resolve, reject) => { + if (!context.permissionLevel) { + return reject( + 'Calling login() without a permissionLevel is not supported by the WalletPluginMultiSig plugin.' + ); + } + resolve({ + chain, + permissionLevel: context.permissionLevel + }); + }) + ); + } + sign( + resolved: ResolvedSigningRequest, + context: TransactContext + ): Cancelable { + return cancelable( + new Promise((resolve) => { + const walletPlugin = defaultWalletPlugins.find( + (plugin) => plugin.id === this.data.session.walletPlugin.id + ); + if (!walletPlugin) { + throw new Error('Wallet plugin not found'); + } + const session = new Session( + { + chain: context.chain, + permissionLevel: PermissionLevel.from({ + actor: this.data.session.actor, + permission: this.data.session.permission + }), + walletPlugin + }, + { + ui: context.ui + } + ); + const transaction = Transaction.from(resolved.transaction); + context.client.v1.chain.get_account(resolved.signer.actor).then((account) => { + const permission = account.permissions.find((p) => + p.perm_name.equals(resolved.signer.permission) + ); + if (!permission) { + throw new Error('Requested permission not found'); + } + const requested = permission.required_auth.accounts.map((a) => a.permission); + const msig = new MsigContract({ client: context.client }); + const action = msig.action( + 'propose', + { + proposal_name: generateRandomName(), + proposer: session.actor, + requested, + trx: transaction + }, + { + authorization: [session.permissionLevel] + } + ); + session.transact({ action }, { broadcast: false }).then((result) => { + resolve({ + resolved: result.resolved, + signatures: result.signatures + }); + }); + }); + }) + ); + } +} + const defaultWalletPlugins: WalletPlugin[] = [ new WalletPluginAnchor(), new WalletPluginMetaMask(), new WalletPluginScatter(), new WalletPluginTokenPocket(), - new WalletPluginWombat() + new WalletPluginWombat(), + new WalletPluginMultiSig() ]; const transactPlugins: TransactPlugin[] = [ @@ -116,10 +244,28 @@ export class WharfState { transactPlugins } ); + $inspect(this.chainsSession); $effect.root(() => { $effect(() => { + console.log('effect chainSessions setItem', JSON.stringify(this.chainsSession)); localStorage.setItem('chainsSession', JSON.stringify(this.chainsSession)); }); + $effect(() => { + console.log('effect session', JSON.stringify(this.session?.actor)); + if (this.sessionKit && this.session) { + // Don't allow multi-sig wallets to recursively setup more multi-sig wallets + if (this.session.walletPlugin.id !== 'wallet-plugin-multisig') { + const index = this.sessionKit.walletPlugins.findIndex( + (plugin) => plugin.id === 'wallet-plugin-multisig' + ); + if (index !== -1) { + this.sessionKit.walletPlugins.splice(index, 1); + } + this.sessionKit.walletPlugins.push(new WalletPluginMultiSig(this.session.serialize())); + console.log('added multisig wallet plugin for ', String(this.session.actor)); + } + } + }); }); } @@ -173,6 +319,9 @@ export class WharfState { if (!this.sessionKit) { throw new Error('User not initialized'); } + // if (args?.walletPlugin.id === 'wallet-plugin-multisig') { + // this.sessionKit.walletPlugins.push(new WalletPluginMultiSig(args.walletPlugin.data.session)); + // } // TODO: If the current account matches the account we're switching to, just return the current session const session = await this.sessionKit.restore(args, options); if (session) { @@ -201,6 +350,23 @@ export class WharfState { this.session = undefined; } + public async multisig(permissionLevel: PermissionLevel) { + if (!this.session || !this.chain || !this.sessionKit) { + throw new Error('Session or chain not initialized'); + } + const { session } = await this.sessionKit.login({ + arbitrary: { + session: this.session.serialize() + }, + chain: this.chain, + permissionLevel, + walletPlugin: 'wallet-plugin-multisig' + }); + this.session = session; + this.chainsSession[String(session.chain.id)] = session.serialize(); + this.sessions = await this.sessionKit.getSessions(); + } + async transact(args: TransactArgs, options?: TransactOptions): Promise { if (!this.session) { throw new Error('No active session available to transact with.'); diff --git a/src/lib/utils/random.ts b/src/lib/utils/random.ts new file mode 100644 index 00000000..65b1ac43 --- /dev/null +++ b/src/lib/utils/random.ts @@ -0,0 +1,10 @@ +export function generateRandomName(length = 12) { + let name = ''; + const characters = 'abcdefghijklmnopqrstuvwxyz12345'; + + for (let i = 0; i < length; i += 1) { + name += characters.charAt(Math.floor(Math.random() * characters.length)); + } + + return name; +} diff --git a/src/routes/[network]/(account)/multisig/+page.svelte b/src/routes/[network]/(account)/multisig/+page.svelte new file mode 100644 index 00000000..0e8835eb --- /dev/null +++ b/src/routes/[network]/(account)/multisig/+page.svelte @@ -0,0 +1,444 @@ + + +{#snippet AccountName()} +
+ + +
+{/snippet} + +{#snippet PublicKeys()} +
+ + +
+ +
+ + +
+{/snippet} + +{#snippet RAMBytes()} +
+
+ +
+ {accountName} +
+
+ +
+ {ownerPublicKey} +
+
+ +
+ {activePublicKey} +
+
+ +
+ {ramBytes} +
+
+
+
+ + +
+
+ {#if ramBytesUseCustom} + + + + + {#if context.settings.data.advancedMode} +
+ + +
+ {/if} +
+
+ {/if} +
+
+{/snippet} + +{#snippet Create()} +
+ {@render RAMBytes()} + + {#if !context.wharf.session} +

+ You must be logged in with an existing account in order to create another account on this + page. +

+ {/if} +
+{/snippet} + +{#snippet ButtonGroup()} +
+ {#if f.current === 'account' || f.current === 'complete'} + + {:else} + + {/if} + + +
+{/snippet} + +{#snippet TransactError()} +
+

An error occurred

+

{transactError}

+
+{/snippet} + +{#snippet TransactResult()} +
+

Account Created

+

The account was successfully created.

+
+{/snippet} + + + + {@render AccountName()} + + {@render PublicKeys()} + + {@render Create()} + + {@render TransactResult()} + + {@render TransactError()} + + {@render ButtonGroup()} + + + +{#if context.settings.data.debugMode} +

{m.common_debugging()}

+ {JSON.stringify( + { + ramCost, + ramBytesUseCustom, + ramBytesUseTransfer, + values: { + accountName, + activePublicKey, + ownerPublicKey, + ramBytes + }, + valid: { + accountValid, + activePublicKeyValid, + ownerPublicKeyValid, + ramBytesValid + } + }, + undefined, + 2 + )} +{/if} diff --git a/src/routes/[network]/(account)/multisig/+page.ts b/src/routes/[network]/(account)/multisig/+page.ts new file mode 100644 index 00000000..0e1d2813 --- /dev/null +++ b/src/routes/[network]/(account)/multisig/+page.ts @@ -0,0 +1,12 @@ +import type { PageLoad } from './$types'; + +export const load: PageLoad = async () => { + return { + title: 'Multi-Sig Account', + subtitle: '', + pageMetaTags: { + title: 'Multi-Sig Account', + description: '' + } + }; +}; diff --git a/src/routes/[network]/(explorer)/account/[name]/+layout.svelte b/src/routes/[network]/(explorer)/account/[name]/+layout.svelte index 62b56d3e..71a40d4c 100644 --- a/src/routes/[network]/(explorer)/account/[name]/+layout.svelte +++ b/src/routes/[network]/(explorer)/account/[name]/+layout.svelte @@ -1,5 +1,5 @@ diff --git a/src/routes/[network]/(explorer)/account/[name]/authority/+page.svelte b/src/routes/[network]/(explorer)/account/[name]/authority/+page.svelte new file mode 100644 index 00000000..bc566e61 --- /dev/null +++ b/src/routes/[network]/(explorer)/account/[name]/authority/+page.svelte @@ -0,0 +1,52 @@ + + +{#await data.authorizations then authorizations} + {#each authorizations.accounts as auth, i} + {@const permission = PermissionLevel.from(`${auth.account_name}@${auth.permission_name}`)} + +
+
+ + {permission} + +

+ Weight of {auth.weight} and threshold of {auth.threshold}. +

+
+
+ {#if isCurrentUser} + + {/if} +
+
+
+ {/each} +{/await} diff --git a/src/routes/[network]/(explorer)/account/[name]/authority/+page.ts b/src/routes/[network]/(explorer)/account/[name]/authority/+page.ts new file mode 100644 index 00000000..50a3f04b --- /dev/null +++ b/src/routes/[network]/(explorer)/account/[name]/authority/+page.ts @@ -0,0 +1,23 @@ +import type { PageLoad } from './$types'; +import * as m from '$lib/paraglide/messages'; + +export const load: PageLoad = async ({ params, parent }) => { + const { network } = await parent(); + const authorizations = network.client.v1.chain.get_accounts_by_authorizers({ + accounts: [params.name] + }); + return { + authorizations, + subtitle: 'Accounts allowing this account to sign on their behalf', + pageMetaTags: { + title: m.explorer_account_balances_meta_title({ + account: params.name, + network: network.chain.name + }), + description: m.explorer_account_balances_meta_description({ + account: params.name, + network: network.chain.name + }) + } + }; +}; diff --git a/src/routes/[network]/(explorer)/account/[name]/proposals/+page.svelte b/src/routes/[network]/(explorer)/account/[name]/proposals/+page.svelte index 4abfd9e1..367afea1 100644 --- a/src/routes/[network]/(explorer)/account/[name]/proposals/+page.svelte +++ b/src/routes/[network]/(explorer)/account/[name]/proposals/+page.svelte @@ -1,15 +1,24 @@ -{#each data.account.proposals as proposal} +{#each proposals as proposal} -

- {proposal.proposal_name} + Proposal ID +

+ + {proposal.proposal_name} +

+{:else} + +

No proposals found.

+
{/each} diff --git a/src/routes/[network]/(explorer)/contract/[contract]/actions/[action]/[[data]]/+page.svelte b/src/routes/[network]/(explorer)/contract/[contract]/actions/[action]/[[data]]/+page.svelte index cd3558ff..3627972f 100644 --- a/src/routes/[network]/(explorer)/contract/[contract]/actions/[action]/[[data]]/+page.svelte +++ b/src/routes/[network]/(explorer)/contract/[contract]/actions/[action]/[[data]]/+page.svelte @@ -156,6 +156,7 @@ } function clear() { + error = undefined; transactResult = undefined; } diff --git a/src/routes/[network]/(explorer)/msig/[proposer]/[proposal]/+page.svelte b/src/routes/[network]/(explorer)/msig/[proposer]/[proposal]/+page.svelte index 578fec17..06ffd456 100644 --- a/src/routes/[network]/(explorer)/msig/[proposer]/[proposal]/+page.svelte +++ b/src/routes/[network]/(explorer)/msig/[proposer]/[proposal]/+page.svelte @@ -12,6 +12,7 @@ import { ApprovalManager } from './manager.svelte'; import type { ActionDisplayVariants } from '$lib/types'; + import { goto } from '$app/navigation'; let { data } = $props(); @@ -25,6 +26,13 @@ const top21 = data.producers.splice(0, 21); let variant = $derived(context.settings.data.actionDisplayVariant as ActionDisplayVariants); + + async function cancel() { + await manager.cancel(); + goto(`/${data.network}/account/${data.proposal.proposer}/proposals`, { + invalidateAll: true + }); + } @@ -142,10 +150,8 @@ {/if} {#if manager.userIsProposer} - {m.msig_cancel_action()} {/if} From d46b7f22de83e8b45f722ecf97e70fde2832994b Mon Sep 17 00:00:00 2001 From: Aaron Cox Date: Sun, 23 Feb 2025 11:22:23 -0800 Subject: [PATCH 04/36] Added permission when matching current session --- src/lib/components/accountswitch.svelte | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/components/accountswitch.svelte b/src/lib/components/accountswitch.svelte index 69e1063e..9d22afd5 100644 --- a/src/lib/components/accountswitch.svelte +++ b/src/lib/components/accountswitch.svelte @@ -232,7 +232,9 @@
    {#each chainSessions as session} - {@const isCurrent = currentSession?.actor.toString() === session.actor} + {@const isCurrent = + currentSession?.actor.equals(session.actor) && + currentSession?.permission.equals(session.permission)}
  • + {/if} From 3aa1ff39a97ac5d48e1f177a99b0f1bd1aa25d82 Mon Sep 17 00:00:00 2001 From: Aaron Cox Date: Sun, 23 Feb 2025 12:24:39 -0800 Subject: [PATCH 13/36] Removed debug link + added more text --- src/lib/components/transact/summary.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/transact/summary.svelte b/src/lib/components/transact/summary.svelte index 93834c23..79325cc5 100644 --- a/src/lib/components/transact/summary.svelte +++ b/src/lib/components/transact/summary.svelte @@ -46,8 +46,8 @@

    Multi-Sig Proposal Created

    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.

    - /en/jungle4/msig/oracle1.gm/t1p5cc1bx5bm {#each proposals as proposal} -
  • + {#if wallet.id !== 'wallet-plugin-multisig'} +
  • + +
  • + {/if} {/each}
{/if} From 18a396115584e89912d736c6e06e68a6b3c23d8e Mon Sep 17 00:00:00 2001 From: Aaron Cox Date: Sun, 23 Feb 2025 12:25:37 -0800 Subject: [PATCH 15/36] Added account data update timer when in debug mode --- .../(explorer)/account/[name]/+layout.svelte | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/routes/[network]/(explorer)/account/[name]/+layout.svelte b/src/routes/[network]/(explorer)/account/[name]/+layout.svelte index 2f1c9171..ff851e10 100644 --- a/src/routes/[network]/(explorer)/account/[name]/+layout.svelte +++ b/src/routes/[network]/(explorer)/account/[name]/+layout.svelte @@ -4,6 +4,7 @@ import PillGroup from '$lib/components/navigation/pillgroup.svelte'; import type { UnicoveContext } from '$lib/state/client.svelte.js'; import * as m from '$lib/paraglide/messages'; + import dayjs from 'dayjs'; const context = getContext('state'); const { children, data } = $props(); @@ -49,20 +50,33 @@ return items; }); + let updated: ReturnType; + let lastUpdate = $state(0); let refresh: ReturnType; onMount(() => { + updated = setInterval(() => { + const account = dayjs(data.account.last_update); + const current = dayjs(new Date()); + lastUpdate = account.diff(current, 'seconds') * -1; + }, 1000); refresh = setInterval(() => { data.account.refresh(); - }, 10000); + }, 100000); }); onDestroy(() => { + clearInterval(updated); clearInterval(refresh); }); - {@render children()} + +{#if context.settings.data.debugMode} +
+ Account updated {lastUpdate} seconds ago +
+{/if} From 06225196068b7c89941d719ebda8356ffc8bd930 Mon Sep 17 00:00:00 2001 From: Aaron Cox Date: Sun, 23 Feb 2025 12:25:55 -0800 Subject: [PATCH 16/36] Change default update to 5 seconds --- src/routes/[network]/+layout.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/[network]/+layout.svelte b/src/routes/[network]/+layout.svelte index 7896b842..687d88f6 100644 --- a/src/routes/[network]/+layout.svelte +++ b/src/routes/[network]/+layout.svelte @@ -96,8 +96,8 @@ }); // Number of ms between network updates - const ACCOUNT_UPDATE_INTERVAL = Number(env.PUBLIC_ACCOUNT_UPDATE_INTERVAL) || 3_000; - const NETWORK_UPDATE_INTERVAL = Number(env.PUBLIC_NETWORK_UPDATE_INTERVAL) || 3_000; + const ACCOUNT_UPDATE_INTERVAL = Number(env.PUBLIC_ACCOUNT_UPDATE_INTERVAL) || 5_000; + const NETWORK_UPDATE_INTERVAL = Number(env.PUBLIC_NETWORK_UPDATE_INTERVAL) || 5_000; // Default to not show a banner (avoids flash of banner when hidden) let showBanner = $state(false); From a3a9b3cb3be8627a8bfc7849bef47ef70ace42f4 Mon Sep 17 00:00:00 2001 From: Aaron Cox Date: Sun, 23 Feb 2025 20:10:41 -0800 Subject: [PATCH 17/36] Renamed manifest so it won't be picked up --- static/{site.webmanifest => _site.webmanifest} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename static/{site.webmanifest => _site.webmanifest} (100%) diff --git a/static/site.webmanifest b/static/_site.webmanifest similarity index 100% rename from static/site.webmanifest rename to static/_site.webmanifest From 0f0fb0c33b609f0ee53c72ba00f2a293d61355ba Mon Sep 17 00:00:00 2001 From: Aaron Cox Date: Sun, 23 Feb 2025 22:01:07 -0800 Subject: [PATCH 18/36] Removed reference to manifest --- src/app.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.html b/src/app.html index aeb26348..351b9d2b 100644 --- a/src/app.html +++ b/src/app.html @@ -11,7 +11,7 @@ - +