@lumen-dapps-kit/web3 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-AMDZMZJO.js +32 -0
- package/dist/chunk-AMDZMZJO.js.map +1 -0
- package/dist/index.cjs +413 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +143 -0
- package/dist/index.d.ts +143 -0
- package/dist/index.js +376 -0
- package/dist/index.js.map +1 -0
- package/dist/utils.cjs +36 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.cts +39 -0
- package/dist/utils.d.ts +39 -0
- package/dist/utils.js +3 -0
- package/dist/utils.js.map +1 -0
- package/package.json +63 -0
- package/src/components/AddressDisplay.tsx +92 -0
- package/src/components/AmountDisplay.tsx +59 -0
- package/src/components/ChainSelector.tsx +123 -0
- package/src/components/GasIndicator.tsx +47 -0
- package/src/components/NetworkStatus.tsx +49 -0
- package/src/components/TxStatus.tsx +96 -0
- package/src/components/WalletPill.tsx +87 -0
- package/src/index.ts +22 -0
- package/src/utils.ts +88 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn, StatusBadge, PulseDot } from '@lumen-dapps-kit/ui';
|
|
4
|
+
import type { TxState } from '../utils';
|
|
5
|
+
import { truncateAddress } from '../utils';
|
|
6
|
+
|
|
7
|
+
export interface TxStatusProps {
|
|
8
|
+
state: TxState;
|
|
9
|
+
/** Tx hash, when available. */
|
|
10
|
+
hash?: string;
|
|
11
|
+
/** Optional block explorer URL to link to. */
|
|
12
|
+
explorerUrl?: string;
|
|
13
|
+
/** Optional title (defaults from state). */
|
|
14
|
+
title?: string;
|
|
15
|
+
/** Confirmations seen so far. */
|
|
16
|
+
confirmations?: number;
|
|
17
|
+
/** Required confirmations target. */
|
|
18
|
+
requiredConfirmations?: number;
|
|
19
|
+
className?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const COPY: Record<TxState, { title: string; tone: 'ok' | 'warn' | 'err' | 'data' | 'muted'; live: boolean }> = {
|
|
23
|
+
idle: { title: 'IDLE', tone: 'muted', live: false },
|
|
24
|
+
preparing: { title: 'PREPARING', tone: 'data', live: true },
|
|
25
|
+
'awaiting-signature': { title: 'AWAITING SIGNATURE', tone: 'warn', live: true },
|
|
26
|
+
pending: { title: 'PENDING', tone: 'data', live: true },
|
|
27
|
+
confirmed: { title: 'CONFIRMED', tone: 'ok', live: false },
|
|
28
|
+
failed: { title: 'FAILED', tone: 'err', live: false },
|
|
29
|
+
rejected: { title: 'REJECTED', tone: 'err', live: false },
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* TxStatus — transaction lifecycle indicator. Six canonical states, each
|
|
34
|
+
* with its own tone + dot/badge. Pair with wagmi's `useWaitForTransactionReceipt`
|
|
35
|
+
* to drive state, or any custom signer flow.
|
|
36
|
+
*/
|
|
37
|
+
export const TxStatus = ({
|
|
38
|
+
state,
|
|
39
|
+
hash,
|
|
40
|
+
explorerUrl,
|
|
41
|
+
title,
|
|
42
|
+
confirmations,
|
|
43
|
+
requiredConfirmations,
|
|
44
|
+
className,
|
|
45
|
+
}: TxStatusProps) => {
|
|
46
|
+
const copy = COPY[state];
|
|
47
|
+
const label = title ?? copy.title;
|
|
48
|
+
const link =
|
|
49
|
+
hash && explorerUrl
|
|
50
|
+
? `${explorerUrl.replace(/\/$/, '')}/tx/${hash}`
|
|
51
|
+
: undefined;
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
className={cn(
|
|
56
|
+
'flex items-center justify-between hairline px-3 py-2.5',
|
|
57
|
+
className
|
|
58
|
+
)}
|
|
59
|
+
>
|
|
60
|
+
<div className="flex items-center gap-2.5">
|
|
61
|
+
{copy.live ? (
|
|
62
|
+
<PulseDot tone={copy.tone === 'muted' ? 'data' : copy.tone} />
|
|
63
|
+
) : (
|
|
64
|
+
<StatusBadge tone={copy.tone} dot>
|
|
65
|
+
{''}
|
|
66
|
+
</StatusBadge>
|
|
67
|
+
)}
|
|
68
|
+
<span className="mono text-[11px] text-ink-900">{label}</span>
|
|
69
|
+
</div>
|
|
70
|
+
<div className="flex items-center gap-3 mono text-[10.5px] text-ink-700">
|
|
71
|
+
{typeof confirmations === 'number' &&
|
|
72
|
+
typeof requiredConfirmations === 'number' && (
|
|
73
|
+
<span className="tabular-nums">
|
|
74
|
+
{confirmations}/{requiredConfirmations} CONF
|
|
75
|
+
</span>
|
|
76
|
+
)}
|
|
77
|
+
{hash && (
|
|
78
|
+
link ? (
|
|
79
|
+
<a
|
|
80
|
+
href={link}
|
|
81
|
+
target="_blank"
|
|
82
|
+
rel="noreferrer"
|
|
83
|
+
className="text-ink-800 hover:text-white underline-offset-2 hover:underline"
|
|
84
|
+
>
|
|
85
|
+
{truncateAddress(hash, { head: 6, tail: 4 })}
|
|
86
|
+
</a>
|
|
87
|
+
) : (
|
|
88
|
+
<span className="text-ink-800">
|
|
89
|
+
{truncateAddress(hash, { head: 6, tail: 4 })}
|
|
90
|
+
</span>
|
|
91
|
+
)
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState } from 'react';
|
|
4
|
+
import { cn, Button } from '@lumen-dapps-kit/ui';
|
|
5
|
+
import { truncateAddress } from '../utils';
|
|
6
|
+
|
|
7
|
+
export interface WalletPillProps {
|
|
8
|
+
/** Address — undefined renders the connect CTA. */
|
|
9
|
+
address?: string;
|
|
10
|
+
/** Display name (ENS, etc). */
|
|
11
|
+
name?: string;
|
|
12
|
+
/** Active chain symbol shown after the address. */
|
|
13
|
+
chainSymbol?: string;
|
|
14
|
+
onConnect?: () => void;
|
|
15
|
+
/** When provided, the pill becomes a click target (opens an account drawer, etc).
|
|
16
|
+
* When omitted, the pill defaults to click-to-copy. */
|
|
17
|
+
onClick?: () => void;
|
|
18
|
+
/** Loading state during connect/reconnect handshake. */
|
|
19
|
+
pending?: boolean;
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* WalletPill — top-right wallet affordance. Renders a CONNECT button when
|
|
25
|
+
* disconnected; a pill with truncated address (+ optional ENS, chain symbol)
|
|
26
|
+
* when connected. Wallet-library agnostic — pass state in via props.
|
|
27
|
+
*/
|
|
28
|
+
export const WalletPill = ({
|
|
29
|
+
address,
|
|
30
|
+
name,
|
|
31
|
+
chainSymbol,
|
|
32
|
+
onConnect,
|
|
33
|
+
onClick,
|
|
34
|
+
pending,
|
|
35
|
+
className,
|
|
36
|
+
}: WalletPillProps) => {
|
|
37
|
+
const [copied, setCopied] = useState(false);
|
|
38
|
+
|
|
39
|
+
const handleCopy = useCallback(async () => {
|
|
40
|
+
if (!address) return;
|
|
41
|
+
try {
|
|
42
|
+
await navigator.clipboard.writeText(address);
|
|
43
|
+
setCopied(true);
|
|
44
|
+
setTimeout(() => setCopied(false), 1200);
|
|
45
|
+
} catch {
|
|
46
|
+
/* noop */
|
|
47
|
+
}
|
|
48
|
+
}, [address]);
|
|
49
|
+
|
|
50
|
+
if (!address) {
|
|
51
|
+
return (
|
|
52
|
+
<Button
|
|
53
|
+
variant="primary"
|
|
54
|
+
size="sm"
|
|
55
|
+
onClick={onConnect}
|
|
56
|
+
disabled={pending}
|
|
57
|
+
className={cn(className)}
|
|
58
|
+
>
|
|
59
|
+
{pending ? 'CONNECTING…' : 'CONNECT WALLET'}
|
|
60
|
+
</Button>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const handleClick = onClick ?? handleCopy;
|
|
65
|
+
const label = copied ? 'COPIED' : name || truncateAddress(address);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<button
|
|
69
|
+
type="button"
|
|
70
|
+
onClick={handleClick}
|
|
71
|
+
title={address}
|
|
72
|
+
className={cn(
|
|
73
|
+
'flex items-center gap-2 px-3 h-8 hairline mono text-[11px] hover:bg-white/5 transition-colors',
|
|
74
|
+
className
|
|
75
|
+
)}
|
|
76
|
+
>
|
|
77
|
+
<span className="w-1.5 h-1.5 bg-white" aria-hidden="true" />
|
|
78
|
+
<span className="text-ink-900">{label}</span>
|
|
79
|
+
{chainSymbol && (
|
|
80
|
+
<>
|
|
81
|
+
<span className="text-ink-700">·</span>
|
|
82
|
+
<span className="text-ink-700">{chainSymbol}</span>
|
|
83
|
+
</>
|
|
84
|
+
)}
|
|
85
|
+
</button>
|
|
86
|
+
);
|
|
87
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export {
|
|
2
|
+
AddressDisplay,
|
|
3
|
+
type AddressDisplayProps,
|
|
4
|
+
} from './components/AddressDisplay';
|
|
5
|
+
export { WalletPill, type WalletPillProps } from './components/WalletPill';
|
|
6
|
+
export { NetworkStatus, type NetworkStatusProps } from './components/NetworkStatus';
|
|
7
|
+
export {
|
|
8
|
+
ChainSelector,
|
|
9
|
+
type ChainSelectorProps,
|
|
10
|
+
} from './components/ChainSelector';
|
|
11
|
+
export { GasIndicator, type GasIndicatorProps } from './components/GasIndicator';
|
|
12
|
+
export { TxStatus, type TxStatusProps } from './components/TxStatus';
|
|
13
|
+
export { AmountDisplay, type AmountDisplayProps } from './components/AmountDisplay';
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
truncateAddress,
|
|
17
|
+
formatGwei,
|
|
18
|
+
formatUnits,
|
|
19
|
+
type TxState,
|
|
20
|
+
type ChainBrief,
|
|
21
|
+
type TruncateAddressOpts,
|
|
22
|
+
} from './utils';
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chain-agnostic formatting helpers. No wallet library required.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface TruncateAddressOpts {
|
|
6
|
+
/** Characters to show after the 0x prefix. */
|
|
7
|
+
head?: number;
|
|
8
|
+
/** Characters to show at the tail. */
|
|
9
|
+
tail?: number;
|
|
10
|
+
/** Separator. Defaults "…". */
|
|
11
|
+
sep?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* truncateAddress("0x7F3a2bC9...9C2e") -> "0x7F3a…9C2e"
|
|
16
|
+
* Works for EVM (0x-prefixed) and any other string-based addressing scheme.
|
|
17
|
+
*/
|
|
18
|
+
export const truncateAddress = (
|
|
19
|
+
address: string,
|
|
20
|
+
{ head = 4, tail = 4, sep = '…' }: TruncateAddressOpts = {}
|
|
21
|
+
): string => {
|
|
22
|
+
if (!address) return '';
|
|
23
|
+
const hasPrefix = address.startsWith('0x');
|
|
24
|
+
const prefix = hasPrefix ? '0x' : '';
|
|
25
|
+
const body = hasPrefix ? address.slice(2) : address;
|
|
26
|
+
if (body.length <= head + tail) return address;
|
|
27
|
+
return `${prefix}${body.slice(0, head)}${sep}${body.slice(-tail)}`;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Format wei (bigint or string) as a gwei value with a fixed number of decimals.
|
|
32
|
+
*/
|
|
33
|
+
export const formatGwei = (
|
|
34
|
+
wei: bigint | string | number,
|
|
35
|
+
decimals = 2
|
|
36
|
+
): string => {
|
|
37
|
+
const n = typeof wei === 'bigint' ? Number(wei) : Number(wei);
|
|
38
|
+
const gwei = n / 1e9;
|
|
39
|
+
return gwei.toLocaleString('en-US', {
|
|
40
|
+
minimumFractionDigits: 0,
|
|
41
|
+
maximumFractionDigits: decimals,
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Format a base-unit bigint as a human-readable token amount.
|
|
47
|
+
* formatUnits(1500000000000000000n, 18) -> "1.5"
|
|
48
|
+
*/
|
|
49
|
+
export const formatUnits = (
|
|
50
|
+
value: bigint | string,
|
|
51
|
+
decimals: number,
|
|
52
|
+
displayDecimals = 4
|
|
53
|
+
): string => {
|
|
54
|
+
const v = typeof value === 'string' ? BigInt(value) : value;
|
|
55
|
+
const negative = v < 0n;
|
|
56
|
+
const abs = negative ? -v : v;
|
|
57
|
+
const base = 10n ** BigInt(decimals);
|
|
58
|
+
const whole = abs / base;
|
|
59
|
+
const frac = abs % base;
|
|
60
|
+
const fracStr = frac
|
|
61
|
+
.toString()
|
|
62
|
+
.padStart(decimals, '0')
|
|
63
|
+
.slice(0, displayDecimals)
|
|
64
|
+
.replace(/0+$/, '');
|
|
65
|
+
const out = fracStr ? `${whole}.${fracStr}` : `${whole}`;
|
|
66
|
+
return negative ? `-${out}` : out;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type TxState =
|
|
70
|
+
| 'idle'
|
|
71
|
+
| 'preparing'
|
|
72
|
+
| 'awaiting-signature'
|
|
73
|
+
| 'pending'
|
|
74
|
+
| 'confirmed'
|
|
75
|
+
| 'failed'
|
|
76
|
+
| 'rejected';
|
|
77
|
+
|
|
78
|
+
export interface ChainBrief {
|
|
79
|
+
id: number | string;
|
|
80
|
+
/** Short label, e.g. "ETH", "BASE", "SOL". */
|
|
81
|
+
symbol: string;
|
|
82
|
+
/** Full name, e.g. "Ethereum Mainnet". */
|
|
83
|
+
name: string;
|
|
84
|
+
/** Optional accent color (hex). */
|
|
85
|
+
color?: string;
|
|
86
|
+
/** True if this chain is a testnet. */
|
|
87
|
+
testnet?: boolean;
|
|
88
|
+
}
|