Skip to content
Draft
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
36 changes: 16 additions & 20 deletions app/vibenet/demos/b20/components/AnnouncementModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,31 @@ import type { TokenAccess, TokenInfo } from '../lib/types';
import { CopyPromptButton } from './CopyPromptButton';
import { ErrorNote, Field, Input, Notice } from './primitives';

type AnnouncementModuleProps = {
open: boolean;
onClose: () => void;
token: TokenInfo | null;
tokenAccess: TokenAccess;
wallet: Address | null;
onSend: (label: string, to: Address, data: Hex, action: string) => Promise<Hex | null>;
};

// Disclosure-only announcement flow, presented through the shared TransactionModal.
// Publishing requires OPERATOR_ROLE and an Asset token (Stablecoins do not support
// announcements).
export function AnnouncementModule({
export function AnnouncementModule(props: AnnouncementModuleProps) {
if (!props.open) return null;
return <AnnouncementModuleInner {...props} />;
}

function AnnouncementModuleInner({
open,
onClose,
token,
tokenAccess,
wallet,
onSend,
}: {
open: boolean;
onClose: () => void;
token: TokenInfo | null;
tokenAccess: TokenAccess;
wallet: Address | null;
onSend: (label: string, to: Address, data: Hex, action: string) => Promise<Hex | null>;
}) {
}: AnnouncementModuleProps) {
const [id, setId] = useState('');
const [description, setDescription] = useState('');
const [uri, setUri] = useState('');
Expand All @@ -41,19 +48,8 @@ export function AnnouncementModule({
const [error, setError] = useState<string | null>(null);
const [txHash, setTxHash] = useState<Hex | null>(null);

const reset = () => {
setStep('build');
setFinalizing(false);
setError(null);
setTxHash(null);
setId('');
setDescription('');
setUri('');
};

const handleClose = () => {
if (finalizing) return;
reset();
onClose();
};

Expand Down
53 changes: 25 additions & 28 deletions app/vibenet/demos/b20/components/AssignPolicyModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,41 +13,40 @@ import type { TokenInfo } from '../lib/types';

export type PendingAssignment = { scope: string; scopeLabel: string; policyId: bigint; policyLabel: string };

type AssignPolicyModalProps = {
open: boolean;
onClose: () => void;
token: TokenInfo | null;
assignment: PendingAssignment | null;
onSend: (label: string, to: Address, data: Hex, action: string) => Promise<Hex | null>;
};

// Assign (or clear) a policy on a token feature, through the shared
// TransactionModal so the change is reviewed and confirmed like every other send.
export function AssignPolicyModal({
export function AssignPolicyModal(props: AssignPolicyModalProps) {
if (!props.open || !props.assignment) return null;
return <AssignPolicyInner {...props} assignment={props.assignment} />;
}

function AssignPolicyInner({
open,
onClose,
token,
assignment,
onSend,
}: {
open: boolean;
onClose: () => void;
token: TokenInfo | null;
assignment: PendingAssignment | null;
onSend: (label: string, to: Address, data: Hex, action: string) => Promise<Hex | null>;
}) {
}: Omit<AssignPolicyModalProps, 'assignment'> & { assignment: PendingAssignment }) {
const [step, setStep] = useState<TxStep>('build');
const [finalizing, setFinalizing] = useState(false);
const [error, setError] = useState<string | null>(null);
const [txHash, setTxHash] = useState<Hex | null>(null);

const reset = () => {
setStep('build');
setFinalizing(false);
setError(null);
setTxHash(null);
};

const handleClose = () => {
if (finalizing) return;
reset();
onClose();
};

const submit = async () => {
if (!token || !assignment) return;
if (!token) return;
setError(null);
setStep('submitted');
setFinalizing(true);
Expand Down Expand Up @@ -76,7 +75,7 @@ export function AssignPolicyModal({
error={error ?? undefined}
result={txHash ? { txHash } : null}
titles={{ build: 'Assign Policy', submitted: 'Assign Policy' }}
canProceed={Boolean(assignment)}
canProceed
proceedLabel="Assign"
onProceed={() => void submit()}
onSubmittedBack={() => {
Expand All @@ -90,20 +89,18 @@ export function AssignPolicyModal({
<div className="flex flex-col items-center gap-1">
<Text variant="title3">Policy updated</Text>
<Text variant="label.regular" tone="muted">
Updated “{assignment?.scopeLabel}” to {assignment?.policyLabel}.
Updated “{assignment.scopeLabel}” to {assignment.policyLabel}.
</Text>
</div>
)}
buildBody={
assignment ? (
<ul className="flex flex-col gap-2">
<CallRow index={1}>
<span className="font-normal">{assignment.scopeLabel}</span>
<ReviewArrow />
<span className="text-bds-gray-70 dark:text-bds-gray-30">{assignment.policyLabel}</span>
</CallRow>
</ul>
) : null
<ul className="flex flex-col gap-2">
<CallRow index={1}>
<span className="font-normal">{assignment.scopeLabel}</span>
<ReviewArrow />
<span className="text-bds-gray-70 dark:text-bds-gray-30">{assignment.policyLabel}</span>
</CallRow>
</ul>
}
/>
);
Expand Down
41 changes: 18 additions & 23 deletions app/vibenet/demos/b20/components/DeployModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,35 @@ import {
import type { CreatedToken } from '../lib/types';
import { ErrorNote, Field, Input } from './primitives';

type DeployModuleProps = {
open: boolean;
onClose: () => void;
wallet: Address | null;
onSendCalls: (label: string, calls: Array<{ to: Address; data: Hex }>, action: string) => Promise<Hex | null>;
onCreated: (token: CreatedToken) => Promise<void>;
/** Guided flow: after a stablecoin is created, jump to a first payment in it. */
onFirstPayment?: () => void;
};

// Create-token flow, presented through the shared TransactionModal: the form is
// the build step and a successful deploy shows a compact custom success view.
// The whole deployment runs as ONE atomic EIP-8130 transaction whose calls
// create the bare token, then grant roles, apply options, and mint — all in a
// single transaction (the 8130 account executes the calls in order, so the
// freshly deployed token is callable by the calls that follow it).
export function DeployModule({
export function DeployModule(props: DeployModuleProps) {
if (!props.open) return null;
return <DeployModuleInner {...props} />;
}

function DeployModuleInner({
open,
onClose,
wallet,
onSendCalls,
onCreated,
onFirstPayment,
}: {
open: boolean;
onClose: () => void;
wallet: Address | null;
onSendCalls: (label: string, calls: Array<{ to: Address; data: Hex }>, action: string) => Promise<Hex | null>;
onCreated: (token: CreatedToken) => Promise<void>;
/** Guided flow: after a stablecoin is created, jump to a first payment in it. */
onFirstPayment?: () => void;
}) {
}: DeployModuleProps) {
const [variant, setVariant] = useState<'asset' | 'stablecoin'>('asset');
// Advanced mode reveals the salt, supply cap, and token-info link — hidden by
// default so the common path stays short (mirrors the account create modal's
Expand All @@ -71,17 +78,8 @@ export function DeployModule({
const [error, setError] = useState<string | null>(null);
const [createdToken, setCreatedToken] = useState<CreatedToken | null>(null);

const reset = () => {
setStep('build');
setFinalizing(false);
setError(null);
setCreatedToken(null);
setSalt('');
};

const handleClose = () => {
if (finalizing) return;
reset();
onClose();
};

Expand Down Expand Up @@ -215,10 +213,7 @@ export function DeployModule({
<Button
className="mt-2"
size="sm"
onClick={() => {
reset();
onFirstPayment();
}}
onClick={onFirstPayment}
>
Send your first payment in {createdToken.symbol} →
</Button>
Expand Down
43 changes: 21 additions & 22 deletions app/vibenet/demos/b20/components/GasModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,49 +12,48 @@ import { TransactionModal, type TxStep } from '../../_shared/TransactionModal';
import type { TokenInfo } from '../lib/types';
import { Notice } from './primitives';

type GasModuleProps = {
open: boolean;
onClose: () => void;
token: TokenInfo | null;
/** ETH being sent by the demo transaction, e.g. "0.001". */
ethAmount: string;
/** Recipient of the demo ETH transfer. */
recipient: string;
/** Flat per-transaction fee in token terms, e.g. "0.10". */
fee: string;
/** Sends the demo transaction with its gas paid in the token; resolves to the tx hash. */
onPay: () => Promise<Hex | null>;
};

// "Gas Payments" flow: send a real transaction whose network fee is paid in the
// currently selected stablecoin, not ETH. The account spends the token; the
// demo's own ERC-8168 gas payer spends the ETH that actually buys the gas, so
// the account never needs to hold ETH. This is a demonstration send, not a
// persistent setting — every payment pays its own fee in the token.
export function GasModule({
export function GasModule(props: GasModuleProps) {
if (!props.open) return null;
return <GasModuleInner {...props} />;
}

function GasModuleInner({
open,
onClose,
token,
ethAmount,
recipient,
fee,
onPay,
}: {
open: boolean;
onClose: () => void;
token: TokenInfo | null;
/** ETH being sent by the demo transaction, e.g. "0.001". */
ethAmount: string;
/** Recipient of the demo ETH transfer. */
recipient: string;
/** Flat per-transaction fee in token terms, e.g. "0.10". */
fee: string;
/** Sends the demo transaction with its gas paid in the token; resolves to the tx hash. */
onPay: () => Promise<Hex | null>;
}) {
}: GasModuleProps) {
const [step, setStep] = useState<TxStep>('build');
const [finalizing, setFinalizing] = useState(false);
const [error, setError] = useState<string | null>(null);
const [txHash, setTxHash] = useState<Hex | null>(null);

const isStablecoin = token?.variant === 'stablecoin';

const reset = () => {
setStep('build');
setFinalizing(false);
setError(null);
setTxHash(null);
};

const handleClose = () => {
if (finalizing) return;
reset();
onClose();
};

Expand Down
34 changes: 15 additions & 19 deletions app/vibenet/demos/b20/components/MemoModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,28 @@ import type { TokenInfo } from '../lib/types';
import { CopyPromptButton } from './CopyPromptButton';
import { ErrorNote, Field, Input } from './primitives';

type MemoModuleProps = {
open: boolean;
onClose: () => void;
token: TokenInfo | null;
addressBook: AddressBookEntry[];
onSend: (label: string, to: Address, data: Hex, action: string) => Promise<Hex | null>;
};

// Send-with-memo flow, presented through the shared TransactionModal. It attaches
// a bytes32 reference to a B20 transfer so the transaction can be reconciled later.
export function MemoModule({
export function MemoModule(props: MemoModuleProps) {
if (!props.open) return null;
return <MemoModuleInner {...props} />;
}

function MemoModuleInner({
open,
onClose,
token,
addressBook,
onSend,
}: {
open: boolean;
onClose: () => void;
token: TokenInfo | null;
addressBook: AddressBookEntry[];
onSend: (label: string, to: Address, data: Hex, action: string) => Promise<Hex | null>;
}) {
}: MemoModuleProps) {
const [to, setTo] = useState('');
const [value, setValue] = useState('');
const [memo, setMemo] = useState('');
Expand All @@ -38,19 +45,8 @@ export function MemoModule({
const [error, setError] = useState<string | null>(null);
const [txHash, setTxHash] = useState<Hex | null>(null);

const reset = () => {
setStep('build');
setFinalizing(false);
setError(null);
setTxHash(null);
setTo('');
setValue('');
setMemo('');
};

const handleClose = () => {
if (finalizing) return;
reset();
onClose();
};

Expand Down
Loading
Loading