Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
53 changes: 37 additions & 16 deletions apps/aquachain/src/app/components/local-dao/local-dao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
voteCount,
VoteChoice,
} from '@services/local-dao/local-dao';
import { humanizeContractError } from '@services/contract/contract-errors';
import { ModuleShell } from '../module-shell/module-shell';
import { WalletBanner } from '../shared/wallet-banner/wallet-banner';

Expand Down Expand Up @@ -248,6 +249,20 @@ export class LocalDao implements OnInit {
if (!this.selected) {
return;
}
try {
await this.ensureWallet();
await this.refreshWalletVote();
} catch (err: unknown) {
this.toastr.showError(humanizeContractError(err), 'Wallet Connection Failed');
return;
}
if (this.walletVote) {
this.toastr.showError(
`You already voted ${this.walletVote} on this proposal.`,
'Already voted',
);
return;
}
await this.runAction(
() =>
this.dao.vote(
Expand Down Expand Up @@ -341,18 +356,7 @@ export class LocalDao implements OnInit {
this.selected.id,
this.walletAddress,
);
const vote = record.vote;
if (typeof vote === 'object' && vote !== null && !Array.isArray(vote)) {
if ('yes' in vote) {
this.walletVote = 'yes';
} else if ('no' in vote) {
this.walletVote = 'no';
} else {
this.walletVote = 'abstain';
}
} else {
this.walletVote = null;
}
this.walletVote = normalizeVoteChoice(record.vote);
} catch {
this.walletVote = null;
}
Expand Down Expand Up @@ -388,10 +392,7 @@ export class LocalDao implements OnInit {
}
}
} catch (err: unknown) {
this.toastr.showError(
err instanceof Error ? err.message : String(err),
errorTitle,
);
this.toastr.showError(humanizeContractError(err), errorTitle);
} finally {
this.busyAction = false;
}
Expand All @@ -406,3 +407,23 @@ export class LocalDao implements OnInit {
return address;
}
}

function normalizeVoteChoice(
vote: VoteChoice | { yes?: null } | { no?: null } | { abstain?: null },
): VoteChoice | null {
if (typeof vote === 'string') {
return vote;
}
if (vote && typeof vote === 'object') {
if ('yes' in vote) {
return 'yes';
}
if ('no' in vote) {
return 'no';
}
if ('abstain' in vote) {
return 'abstain';
}
}
return null;
}
16 changes: 16 additions & 0 deletions apps/aquachain/src/app/services/contract/contract-errors.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { describe, expect, it } from 'vitest';
import { humanizeContractError } from './contract-errors';

describe('humanizeContractError', () => {
it('maps already voted', () => {
expect(humanizeContractError(new Error('Generic error: Already voted'))).toBe(
'You already voted on this proposal.',
);
});

it('maps voting not ended', () => {
expect(humanizeContractError('Voting period has not ended')).toBe(
'Voting is still open. Finalize after the end time.',
);
});
});
36 changes: 36 additions & 0 deletions apps/aquachain/src/app/services/contract/contract-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/** Map CosmWasm / RPC errors to short user-facing copy. */
export function humanizeContractError(raw: unknown): string {
const text =
raw instanceof Error ? raw.message : typeof raw === 'string' ? raw : String(raw);

const rules: readonly [RegExp, string][] = [
[/Already voted/i, 'You already voted on this proposal.'],
[/Voting period has not ended/i, 'Voting is still open. Finalize after the end time.'],
[/Voting period has ended/i, 'Voting has ended. You can no longer cast a vote.'],
[/Already finalized|Proposal already finalized/i, 'This proposal is already finalized.'],
[/Proposal is not open/i, 'This proposal is no longer open for votes.'],
[/Quorum not reached/i, 'Quorum was not reached. This proposal failed.'],
[/Proposal did not pass/i, 'The proposal did not pass.'],
[/Missing funds|Attached funds are required/i, 'Attach the reward OSMO amount in Keplr.'],
[/Invalid funds|do not match the proposal reward/i, 'Attached OSMO must match the proposal reward.'],
[/Invalid type map/i, 'App version mismatch. Hard-refresh the page and try again.'],
[/Keplr extension not found/i, 'Install and unlock the Keplr extension.'],
[/insufficient funds|spendable balance/i, 'Not enough OSMO for gas or the attached reward.'],
];

for (const [pattern, message] of rules) {
if (pattern.test(text)) {
return message;
}
}

if (text.length > 180) {
const generic = text.match(/Generic error: ([^:]+)/i)?.[1]?.trim();
if (generic) {
return generic;
}
return 'Transaction failed. Check Keplr and try again.';
}

return text;
}
Loading