redesign fixes (#160)
* fix: show error on invalid transactions * fix: leave nonce unset if not changed by the user Co-authored-by: Carlo Sala <carlosalag@protonmail.com> * avoid spamming nonce requests --------- Co-authored-by: Carlo Sala <carlosalag@protonmail.com>
This commit is contained in:
@@ -116,10 +116,18 @@ const ExtrinsicsWorkspaceEntry: FC<{ event: TrackedTransactionEvent }> = ({
|
||||
</div>
|
||||
<div className="contents">
|
||||
<dt className="text-muted-foreground">Hash</dt>
|
||||
<dd className="truncate font-mono flex items-center gap-1">
|
||||
{event.txHash ? shortStr(event.txHash, 8) : ""}
|
||||
<CopyText text={event.txHash} disabled={!event.txHash} size={14} />
|
||||
</dd>
|
||||
{event.txHash ? (
|
||||
<dd className="truncate font-mono flex items-center gap-1">
|
||||
{shortStr(event.txHash, 8)}
|
||||
<CopyText
|
||||
text={event.txHash}
|
||||
disabled={!event.txHash}
|
||||
size={14}
|
||||
/>
|
||||
</dd>
|
||||
) : (
|
||||
<dd />
|
||||
)}
|
||||
</div>
|
||||
{"block" in event ? (
|
||||
<div className="contents">
|
||||
@@ -159,11 +167,11 @@ const ExtrinsicsWorkspaceEntry: FC<{ event: TrackedTransactionEvent }> = ({
|
||||
</dd>
|
||||
</div>
|
||||
) : null}
|
||||
{event.type === "error" ? (
|
||||
{event.type === "error" || event.type === "invalid" ? (
|
||||
<div className="contents">
|
||||
<dt className="text-muted-foreground">Error</dt>
|
||||
<dd className="truncate font-bold font-mono text-red-500">
|
||||
{JSON.stringify(event.value, jsonSerialize)}
|
||||
<dd className="text-xs">
|
||||
<JsonDisplay src={event.value?.error ?? event.value} />
|
||||
</dd>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -12,7 +12,11 @@ import {
|
||||
import { createState } from "@/lib/externalState"
|
||||
import { PolkahubModalBasedManagers } from "@/pages/Accounts/Providers"
|
||||
import { client$, unsafeApi$ } from "@/state/chains/chain.state"
|
||||
import { selectedAccount$ } from "@/state/polkahub"
|
||||
import {
|
||||
getAccountGenericAddress,
|
||||
getAccountPublicKey,
|
||||
selectedAccount$,
|
||||
} from "@/state/polkahub"
|
||||
import { polkadot_people } from "@polkadot-api/descriptors"
|
||||
import {
|
||||
Button,
|
||||
@@ -27,6 +31,7 @@ import {
|
||||
state,
|
||||
SUSPENSE,
|
||||
useStateObservable,
|
||||
withDefault,
|
||||
} from "@react-rxjs/core"
|
||||
import {
|
||||
createSignal,
|
||||
@@ -34,7 +39,7 @@ import {
|
||||
switchMapSuspended,
|
||||
} from "@react-rxjs/utils"
|
||||
import { ChevronLeft, Send, Settings, WalletCards } from "lucide-react"
|
||||
import { AccountId, TxOptions } from "polkadot-api"
|
||||
import { TxOptions } from "polkadot-api"
|
||||
import {
|
||||
ModalContext,
|
||||
PjsWalletButtons,
|
||||
@@ -45,7 +50,7 @@ import { FC, forwardRef, ReactNode, useState } from "react"
|
||||
import {
|
||||
catchError,
|
||||
combineLatest,
|
||||
defer,
|
||||
distinctUntilChanged,
|
||||
map,
|
||||
of,
|
||||
scan,
|
||||
@@ -73,19 +78,17 @@ const customExtensionsCount$ = state(
|
||||
|
||||
const [nonceChanged$, setNonce] = createSignal<string>()
|
||||
const [nonceBlurred$, blurNonce] = createSignal()
|
||||
const chainNonce$ = unsafeApi$.pipe(
|
||||
const chainNonce$ = unsafeApi$.pipeState(
|
||||
switchMapSuspended((api) =>
|
||||
selectedAccount$.pipe(
|
||||
switchMapSuspended((account) =>
|
||||
account?.signer
|
||||
? api.apis.AccountNonceApi.account_nonce(
|
||||
AccountId(42).dec(account.signer.publicKey),
|
||||
{
|
||||
at: "best",
|
||||
},
|
||||
switchMapSuspended((account) => {
|
||||
const address = account && getAccountGenericAddress(account)
|
||||
return address
|
||||
? timer(0, 60_000).pipe(
|
||||
switchMap(() => api.apis.AccountNonceApi.account_nonce(address)),
|
||||
)
|
||||
: [],
|
||||
),
|
||||
: [null]
|
||||
}),
|
||||
liftSuspense(),
|
||||
catchError((ex) => {
|
||||
console.error(ex)
|
||||
@@ -95,45 +98,20 @@ const chainNonce$ = unsafeApi$.pipe(
|
||||
),
|
||||
liftSuspense(),
|
||||
map((v) => (v === SUSPENSE ? null : (v as number))),
|
||||
withDefault(null),
|
||||
)
|
||||
const isIntegerStr = (str: string) => /^\d+$/.test(str)
|
||||
const nonce$ = state(
|
||||
defer(() =>
|
||||
mergeWithKey({
|
||||
chainNonce$,
|
||||
nonceChanged$,
|
||||
nonceBlurred$,
|
||||
}).pipe(
|
||||
scan(
|
||||
(
|
||||
acc: {
|
||||
chain: number | null
|
||||
inputValue: string
|
||||
},
|
||||
v,
|
||||
) => {
|
||||
switch (v.type) {
|
||||
case "chainNonce$":
|
||||
if (v.payload != null)
|
||||
return { chain: v.payload, inputValue: v.payload.toString() }
|
||||
break
|
||||
case "nonceBlurred$":
|
||||
if (!isIntegerStr(acc.inputValue)) {
|
||||
return {
|
||||
chain: acc.chain,
|
||||
inputValue: acc.chain?.toString() ?? "",
|
||||
}
|
||||
}
|
||||
break
|
||||
case "nonceChanged$":
|
||||
return { chain: acc.chain, inputValue: v.payload }
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{ chain: null, inputValue: "" },
|
||||
),
|
||||
map((v) => v.inputValue),
|
||||
mergeWithKey({
|
||||
nonceChanged$,
|
||||
nonceBlurred$,
|
||||
}).pipe(
|
||||
scan(
|
||||
(acc, v) =>
|
||||
v.type === "nonceChanged$" ? v.payload : isIntegerStr(acc) ? acc : "",
|
||||
"",
|
||||
),
|
||||
distinctUntilChanged(),
|
||||
),
|
||||
"",
|
||||
)
|
||||
@@ -178,17 +156,20 @@ const txOptions$ = state(
|
||||
{} satisfies TxOptions<any, any>,
|
||||
)
|
||||
|
||||
const paymentInfo$ = state(
|
||||
export const paymentInfo$ = state(
|
||||
combineLatest([transaction$, selectedAccount$, txOptions$]).pipe(
|
||||
switchMapSuspended(([tx, account, txOptions]) => {
|
||||
if (!tx || !account?.signer) return [null]
|
||||
if (!tx || !account) return [null]
|
||||
|
||||
// Adding a small delay for debouncing quick input changes
|
||||
return timer(200).pipe(
|
||||
switchMap(() =>
|
||||
tx.getPaymentInfo(account.signer!.publicKey, txOptions),
|
||||
tx.getPaymentInfo(getAccountPublicKey(account), txOptions),
|
||||
),
|
||||
catchError(() => of(null)),
|
||||
catchError((ex) => {
|
||||
console.error(ex)
|
||||
return of(null)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
liftSuspense(),
|
||||
@@ -196,16 +177,13 @@ const paymentInfo$ = state(
|
||||
),
|
||||
null,
|
||||
)
|
||||
|
||||
const accountBalance$ = state(
|
||||
combineLatest([selectedAccount$, client$]).pipe(
|
||||
switchMapSuspended(([account, client]) =>
|
||||
account?.signer
|
||||
account
|
||||
? client
|
||||
.getTypedApi(polkadot_people)
|
||||
.query.System.Account.getValue(
|
||||
AccountId().dec(account.signer.publicKey),
|
||||
)
|
||||
.query.System.Account.getValue(getAccountGenericAddress(account))
|
||||
: [],
|
||||
),
|
||||
liftSuspense(),
|
||||
@@ -226,6 +204,7 @@ const accountBalance$ = state(
|
||||
|
||||
export const SubmitExtrinsic = forwardRef<HTMLElement>((_, ref) => {
|
||||
const [account] = useSelectedAccount()
|
||||
const chainNonce = useStateObservable(chainNonce$)
|
||||
const nonce = useStateObservable(nonce$)
|
||||
const mortality = useStateObservable(mortality$)
|
||||
const tip = useStateObservable(tip$)
|
||||
@@ -284,6 +263,7 @@ export const SubmitExtrinsic = forwardRef<HTMLElement>((_, ref) => {
|
||||
type="number"
|
||||
min={0}
|
||||
value={nonce}
|
||||
placeholder={chainNonce?.toString()}
|
||||
onChange={(evt) => setNonce(evt.target.value)}
|
||||
onBlur={blurNonce}
|
||||
className="tabular-nums"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { polkadot_people } from "@polkadot-api/descriptors"
|
||||
import { liftSuspense, state, SUSPENSE, withDefault } from "@react-rxjs/core"
|
||||
import { AccountId, HexString, SS58String } from "polkadot-api"
|
||||
import { AccountId, Binary, HexString, SS58String } from "polkadot-api"
|
||||
import {
|
||||
Account,
|
||||
createLedgerProvider,
|
||||
@@ -214,3 +214,20 @@ export const toggleExtension = async (id: string) => {
|
||||
: [...extensions, id],
|
||||
)
|
||||
}
|
||||
|
||||
export const getAccountPublicKey = (account: Account) =>
|
||||
account.signer
|
||||
? account.signer.publicKey
|
||||
: account.address.startsWith("0x")
|
||||
? Binary.fromHex(account.address)
|
||||
: AccountId().enc(account.address)
|
||||
|
||||
// Important, the SS58 format is not guaranteed. Only to be used for internal queries
|
||||
export const getAccountGenericAddress = (account: Account) =>
|
||||
account.address.startsWith("0x")
|
||||
? account.signer
|
||||
? Binary.toHex(account.signer.publicKey)
|
||||
: account.address
|
||||
: account.signer
|
||||
? AccountId().dec(account.signer.publicKey)
|
||||
: account.address
|
||||
|
||||
Reference in New Issue
Block a user