Skip to content

Commit 848008e

Browse files
committed
Sentiment config + topic creation
1 parent 390deee commit 848008e

7 files changed

Lines changed: 465 additions & 12 deletions

File tree

configs/contracts/sentiment.ts

Lines changed: 94 additions & 12 deletions
Large diffs are not rendered by default.
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
<script lang="ts">
2+
import { Asset, Name } from '@wharfkit/antelope';
3+
import { getContext } from 'svelte';
4+
import { Stack, NameInput, SymbolInput, AssetInput, Button, Label } from 'unicove-components';
5+
import TransactSummary from '$lib/components/transact/summary.svelte';
6+
import TransactError from '$lib/components/transact/error.svelte';
7+
import type { UnicoveContext } from '$lib/state/client.svelte';
8+
9+
const context = getContext<UnicoveContext>('state');
10+
const { data } = $props();
11+
12+
let systemContract: Name = $state(data.config?.system_contract ?? Name.from(''));
13+
let systemContractValid = $state(false);
14+
let tokenContract: Name = $state(data.config?.fees.token.contract ?? Name.from(''));
15+
let tokenContractValid = $state(false);
16+
let tokenAction: Name = $state(data.config?.fees.action ?? Name.from('transfer'));
17+
let tokenActionValid = $state(false);
18+
let tokenSymbol: Asset.Symbol = $state(
19+
data.config?.fees.token.symbol ?? Asset.Symbol.from('4,EOS')
20+
);
21+
let tokenSymbolValid = $state(false);
22+
let feeReceiver: Name = $state(data.config?.fees.receiver ?? Name.from(''));
23+
let feeReceiverValid = $state(false);
24+
let createtopicFee: Asset = $state(data.config?.fees.createtopic ?? Asset.fromUnits(0, '4,EOS'));
25+
let createtopicFeeValid = $state(false);
26+
27+
let error = $state('');
28+
let txid = $state('');
29+
30+
const canSubmit = $derived(
31+
systemContractValid &&
32+
tokenContractValid &&
33+
tokenActionValid &&
34+
tokenSymbolValid &&
35+
feeReceiverValid &&
36+
!!context.wharf.session &&
37+
!context.wharf.transacting
38+
);
39+
40+
async function submit() {
41+
try {
42+
if (!context.wharf.session) return;
43+
44+
const action = context.network.contracts.sentiment.action('setconfig', {
45+
system_contract: systemContract,
46+
token_contract: tokenContract,
47+
token_action: tokenAction,
48+
token_symbol: tokenSymbol,
49+
fee_receiver: feeReceiver,
50+
createtopic_fee: createtopicFee
51+
});
52+
53+
const result = await context.wharf.transact({ action });
54+
txid = String(result?.response?.transaction_id);
55+
if (!txid) {
56+
error = 'no txid';
57+
}
58+
} catch (e) {
59+
error = String(e);
60+
}
61+
}
62+
63+
function reset() {
64+
error = '';
65+
txid = '';
66+
}
67+
</script>
68+
69+
<Stack>
70+
{#if txid}
71+
<TransactSummary transactionId={txid} />
72+
<Button onclick={reset} variant="secondary">Back to Config</Button>
73+
{:else if error}
74+
<TransactError {error} />
75+
<Button onclick={reset}>Try Again</Button>
76+
{:else}
77+
<Stack class="gap-4">
78+
<div class="bg-surface-container-high space-y-4 rounded-xl p-4">
79+
<fieldset class="grid gap-2">
80+
<Label for="system-contract">System Contract</Label>
81+
<NameInput
82+
bind:value={systemContract}
83+
bind:valid={systemContractValid}
84+
id="system-contract"
85+
placeholder="e.g. eosio"
86+
/>
87+
</fieldset>
88+
89+
<fieldset class="grid gap-2">
90+
<Label for="token-contract">Token Contract</Label>
91+
<NameInput
92+
bind:value={tokenContract}
93+
bind:valid={tokenContractValid}
94+
id="token-contract"
95+
placeholder="e.g. eosio.token"
96+
/>
97+
</fieldset>
98+
99+
<fieldset class="grid gap-2">
100+
<Label for="token-action">Token Action</Label>
101+
<NameInput
102+
bind:value={tokenAction}
103+
bind:valid={tokenActionValid}
104+
id="token-action"
105+
placeholder="e.g. transfer"
106+
/>
107+
</fieldset>
108+
109+
<fieldset class="grid gap-2">
110+
<Label for="token-symbol">Token Symbol</Label>
111+
<SymbolInput
112+
bind:value={tokenSymbol}
113+
bind:valid={tokenSymbolValid}
114+
id="token-symbol"
115+
placeholder="e.g. 4,EOS"
116+
/>
117+
</fieldset>
118+
119+
<fieldset class="grid gap-2">
120+
<Label for="fee-receiver">Fee Receiver</Label>
121+
<NameInput
122+
bind:value={feeReceiver}
123+
bind:valid={feeReceiverValid}
124+
id="fee-receiver"
125+
placeholder="e.g. sentiment.gm"
126+
/>
127+
</fieldset>
128+
129+
<fieldset class="grid gap-2">
130+
<Label for="createtopic-fee">Create Topic Fee</Label>
131+
<AssetInput
132+
bind:value={createtopicFee}
133+
bind:valid={createtopicFeeValid}
134+
id="createtopic-fee"
135+
/>
136+
</fieldset>
137+
</div>
138+
139+
{#if !context.wharf.session}
140+
<p class="text-on-surface-variant text-center text-sm">Please log in to update config</p>
141+
{/if}
142+
143+
<Button disabled={!canSubmit} onclick={submit} variant="primary">Set Config</Button>
144+
</Stack>
145+
{/if}
146+
</Stack>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { PageLoad } from './$types';
2+
3+
export const load: PageLoad = async ({ parent }) => {
4+
const { network } = await parent();
5+
6+
const config = await network.contracts.sentiment.table('config').get();
7+
8+
return {
9+
config,
10+
title: 'Sentiment Config',
11+
subtitle: 'Manage sentiment contract configuration'
12+
};
13+
};

src/routes/[[locale]]/[network]/(explorer)/sentiment/topics/+page.svelte

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@
6868
</p>
6969

7070
<Button href={context.urlPath('/staking')} variant="secondary">Stake tokens</Button>
71+
<Button href={context.urlPath('/sentiment/topics/create')} variant="secondary">
72+
Create Topic
73+
</Button>
7174
</Stack>
7275
</Card>
7376
</div>
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<script lang="ts">
2+
import { getContext } from 'svelte';
3+
import { Stack, NameInput, NameValidationError, Button, Label } from 'unicove-components';
4+
import TransactSummary from '$lib/components/transact/summary.svelte';
5+
import TransactError from '$lib/components/transact/error.svelte';
6+
import type { UnicoveContext } from '$lib/state/client.svelte';
7+
import { CreateTopicManager } from './manager.svelte';
8+
9+
const context = getContext<UnicoveContext>('state');
10+
const { data } = $props();
11+
12+
let manager: CreateTopicManager = $state(new CreateTopicManager());
13+
let ready = $derived(manager.canSubmit && !!context.wharf.session && !context.wharf.transacting);
14+
let nameError: import('unicove-components').NameValidationError | undefined = $state();
15+
let nameTouched = $state(false);
16+
const nameErrorMessage = $derived.by(() => {
17+
if (!nameTouched || !nameError) return '';
18+
switch (nameError) {
19+
case NameValidationError.INVALID_CHARACTERS:
20+
return 'Only lowercase letters a-z and digits 1-5 are allowed.';
21+
case NameValidationError.INVALID_LENGTH_MIN:
22+
return 'Topic ID is required.';
23+
case NameValidationError.INVALID_LENGTH_MAX:
24+
return 'Topic ID must be 12 characters or fewer.';
25+
default:
26+
return '';
27+
}
28+
});
29+
30+
$effect(() => {
31+
if (context.account) {
32+
manager.sync(data.network, context.account, context.wharf);
33+
}
34+
});
35+
36+
function resetState() {
37+
manager = new CreateTopicManager();
38+
}
39+
</script>
40+
41+
<Stack>
42+
{#if manager.txid}
43+
<TransactSummary transactionId={manager.txid} />
44+
<Button
45+
href={context.urlPath(`/sentiment/topics/${String(manager.topicId)}`)}
46+
variant="secondary"
47+
>
48+
View Topic
49+
</Button>
50+
<Button href={context.urlPath('/sentiment/topics')} variant="secondary">Back to Topics</Button>
51+
{:else if manager.error}
52+
<TransactError error={manager.error} />
53+
<Button onclick={resetState}>Try Again</Button>
54+
{:else}
55+
<Stack class="gap-4">
56+
<div class="border-outline-variant/30 bg-surface-container-high rounded-xl border p-4">
57+
<p class="text-on-surface text-sm font-medium">Topic Creation Fee</p>
58+
<p class="text-muted mt-1 text-xs leading-relaxed">
59+
Creating a topic costs
60+
<strong class="text-on-surface-variant">{String(data.config.fees.createtopic)}</strong>
61+
</p>
62+
</div>
63+
64+
<div class="bg-surface-container-high space-y-4 rounded-xl p-4">
65+
<fieldset class="grid gap-2">
66+
<Label for="topic-id">Topic ID</Label>
67+
<NameInput
68+
bind:value={manager.topicId}
69+
bind:valid={manager.topicIdValid}
70+
bind:error={nameError}
71+
id="topic-id"
72+
placeholder="Topic ID"
73+
onblur={() => (nameTouched = true)}
74+
/>
75+
{#if nameErrorMessage}
76+
<p class="text-error text-sm">{nameErrorMessage}</p>
77+
{/if}
78+
</fieldset>
79+
80+
<fieldset class="grid gap-2">
81+
<Label for="topic-description">Description</Label>
82+
<textarea
83+
bind:value={manager.description}
84+
id="topic-description"
85+
placeholder="Describe what this topic is about..."
86+
rows="4"
87+
class="border-outline bg-surface rounded-lg border p-3 text-sm"
88+
></textarea>
89+
</fieldset>
90+
</div>
91+
92+
{#if !context.wharf.session}
93+
<p class="text-on-surface-variant text-center text-sm">Please log in to create a topic</p>
94+
{/if}
95+
96+
<Button disabled={!ready} onclick={() => manager.transact(data.config)} variant="primary">
97+
Create Topic
98+
</Button>
99+
</Stack>
100+
{/if}
101+
</Stack>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { error } from '@sveltejs/kit';
2+
import type { PageLoad } from './$types';
3+
4+
export const load: PageLoad = async ({ parent }) => {
5+
const { network } = await parent();
6+
7+
const config = await network.contracts.sentiment.table('config').get();
8+
9+
if (!config || !config.enabled) {
10+
throw error(503, 'Sentiment topic creation is not available');
11+
}
12+
13+
return {
14+
config,
15+
title: 'Create Topic',
16+
subtitle: 'Create a new sentiment topic'
17+
};
18+
};
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { Action, Name } from '@wharfkit/antelope';
2+
import type { AccountState } from '$lib/state/client/account.svelte';
3+
import type { NetworkState } from '$lib/state/network.svelte';
4+
import type { WharfState } from '$lib/state/client/wharf.svelte';
5+
import { PlaceholderAuth } from '@wharfkit/session';
6+
import { Types } from '$lib/wharf/contracts/sentiment';
7+
8+
export class CreateTopicManager {
9+
public network: NetworkState | undefined = $state();
10+
public account: AccountState | undefined = $state();
11+
public wharf: WharfState | undefined = $state();
12+
13+
public topicId: Name = $state(Name.from(''));
14+
public topicIdValid: boolean = $state(false);
15+
public description: string = $state('');
16+
public error: string = $state('');
17+
public txid: string = $state('');
18+
19+
public canSubmit: boolean = $derived.by(() => {
20+
return this.topicIdValid && this.description.length > 0;
21+
});
22+
23+
sync(network: NetworkState, account: AccountState, wharf: WharfState) {
24+
let changed = false;
25+
if (network.chain !== this.network?.chain) {
26+
this.network = network;
27+
changed = true;
28+
}
29+
if (this.account !== account) {
30+
this.account = account;
31+
changed = true;
32+
}
33+
if (changed) {
34+
this.error = '';
35+
this.txid = '';
36+
}
37+
if (wharf !== this.wharf) {
38+
this.wharf = wharf;
39+
}
40+
}
41+
42+
async transact(config: Types.config_row) {
43+
try {
44+
if (!this.network || !this.account || !this.account.name || !this.wharf) {
45+
throw new Error("Can't sign, data not ready");
46+
}
47+
48+
const sentiment = this.network.contracts.sentiment;
49+
const actions: Action[] = [];
50+
51+
const balance = await sentiment
52+
.table('balance', sentiment.account)
53+
.get(String(this.account.name));
54+
55+
if (!balance) {
56+
actions.push(
57+
sentiment.action('open', {
58+
account: PlaceholderAuth.actor
59+
})
60+
);
61+
}
62+
63+
const transferAction = this.network.contracts.token.action('transfer', {
64+
from: PlaceholderAuth.actor,
65+
to: sentiment.account,
66+
quantity: config.fees.createtopic,
67+
memo: 'sentiment topic creation fee'
68+
});
69+
transferAction.account = config.fees.token.contract;
70+
actions.push(transferAction);
71+
72+
actions.push(
73+
sentiment.action('createtopic', {
74+
creator: PlaceholderAuth.actor,
75+
id: this.topicId,
76+
description: this.description,
77+
payment: config.fees.createtopic
78+
})
79+
);
80+
81+
const result = await this.wharf.transact({ actions });
82+
this.txid = String(result?.response?.transaction_id);
83+
if (!this.txid) {
84+
this.error = 'no txid';
85+
}
86+
} catch (error) {
87+
this.error = String(error);
88+
}
89+
}
90+
}

0 commit comments

Comments
 (0)