migrate to polkadot-api v3 🎉🚀 (#156)

Co-authored-by: Victor Oliva <olivarra1@gmail.com>
This commit is contained in:
Carlo Sala
2026-08-24 12:50:13 +02:00
committed by GitHub
co-authored by Victor Oliva
parent 040dae8920
commit 2dec1477a8
21 changed files with 2263 additions and 2542 deletions
@@ -3,7 +3,7 @@ import { TextInputField } from "./codec-components"
import { useGenericSynchronizeInput } from "@/components/useSynchroniseInput"
const getValidation = (value: string) =>
!!value.match(/^[01]+$/) ? null : `Only "0" and "1" allowed`
value.match(/^[01]+$/) ? null : `Only "0" and "1" allowed`
export const CBitSeq: EditBitSeq = ({ value, onValueChanged }) => {
const [inputValue, setInputValue] = useGenericSynchronizeInput<
+5 -7
View File
@@ -8,9 +8,7 @@ import { FC, useEffect, useState } from "react"
import { useParams } from "react-router-dom"
import { trackTx } from "../Extrinsics/ExtrinsicsWorkspaceEntry"
export const TransactionButton: FC<{
tx: Transaction
}> = ({ tx }) => {
export const TransactionButton: FC<{ tx: Transaction }> = ({ tx }) => {
const { accountId } = useParams()
const [selectedAccount] = useSelectedAccount()
const allAccounts = useAvailableAccounts()
@@ -24,7 +22,7 @@ export const TransactionButton: FC<{
? selectedAccount
: Object.values(allAccounts)
.flat()
.find((v) => v.signer && v.address === accountId)
.find((v) => v.txCreator && v.address === accountId)
useEffect(() => {
let cancelled = false
@@ -43,8 +41,8 @@ export const TransactionButton: FC<{
}, [tx])
const submit = async () => {
if (!account?.signer) return
const signed = await tx.sign(account.signer)
if (!account?.txCreator) return
const signed = await tx.create(account.txCreator)
trackTx(signed, tx.decodedCall, account)
}
@@ -52,7 +50,7 @@ export const TransactionButton: FC<{
<div className="flex min-w-48 flex-col gap-2">
<Button
type="button"
disabled={!account?.signer}
disabled={!account?.txCreator}
onClick={submit}
className="h-9 justify-center gap-2"
>
+5 -3
View File
@@ -20,7 +20,7 @@ export const CustomSignedExt: FC = () => {
<Subscribe source$={extSub$}>
<ul className="space-y-2">
{/* TODO: adapt for different extrinsic versions */}
{metadata.extrinsic.signedExtensions[0].map((ext) => (
{metadata.extrinsic.extensionsByVersion[0].map((ext) => (
<ExtensionInput key={ext.identifier} {...ext} />
))}
</ul>
@@ -84,7 +84,7 @@ export const customSignedExtensions$ = combineLatest([
{
/* TODO: adapt for different extrinsic versions */
}
const ext = metadata.extrinsic.signedExtensions[0].find(
const ext = metadata.extrinsic.extensionsByVersion[0].find(
(v) => v.identifier === id,
)
if (!ext) return [id, null!]
@@ -133,7 +133,9 @@ const extSub$ = merge(
combineKeys(
metadata$.pipe(
// TODO: adapt for different extrinsic versions
map((v) => v.extrinsic.signedExtensions[0].map((ext) => ext.identifier)),
map((v) =>
v.extrinsic.extensionsByVersion[0].map((ext) => ext.identifier),
),
),
extensionValue$,
),
@@ -11,7 +11,7 @@ import { chainClient$ } from "@/state/chains/chain.state"
import { shortStr } from "@/utils"
import { state, StateObservable, useStateObservable } from "@react-rxjs/core"
import { Send } from "lucide-react"
import { InvalidTxError, TxBroadcastEvent, TxCallData } from "polkadot-api"
import { InvalidTxError, TxCallData, TxEvent } from "polkadot-api"
import { jsonSerialize, toHex } from "polkadot-api/utils"
import { Account } from "polkahub"
import { FC } from "react"
@@ -20,7 +20,7 @@ import { v4 } from "uuid"
import { BlockStatusIcon } from "../Explorer/Detail/BlockState"
type TrackedTransactionEvent =
| (TxBroadcastEvent & { raw: Uint8Array; callData: TxCallData })
| (TxEvent & { raw: Uint8Array; callData: TxCallData })
| {
type: "invalid" | "error"
value: any
@@ -29,7 +29,12 @@ type TrackedTransactionEvent =
callData: TxCallData
}
const onGoingEvents = new Set(["signed", "broadcasted", "txBestBlocksState"])
const onGoingEvents: Set<TrackedTransactionEvent["type"]> = new Set([
"created",
"broadcasted",
"inBestBlock",
"notInBestBlock",
])
export const trackTx = async (
extrinsic: Uint8Array,
@@ -189,10 +194,12 @@ const getStatus = (event: TrackedTransactionEvent): string => {
return "Unexpected error"
case "invalid":
return `Invalid`
case "txBestBlocksState":
return event.found ? `Found in best block` : "No longer in a best block"
case "signed":
return `Signed`
case "inBestBlock":
return `Found in best block`
case "notInBestBlock":
return "No longer in a best block"
case "created":
return `Created`
case "broadcasted":
return `Broadcasting`
case "finalized":
+5 -1
View File
@@ -26,6 +26,7 @@ import {
} from "rxjs"
import { transaction$, txOptions$ } from "./submit.state"
import { validate$ } from "./validate"
import { getFakeTxCreator } from "polkadot-api/tx-creator"
const accountBalance$ = state(
combineLatest([selectedAccount$, client$]).pipe(
@@ -60,7 +61,10 @@ export const paymentInfo$ = state(
// Adding a small delay for debouncing quick input changes
return timer(200).pipe(
switchMap(() =>
tx.getPaymentInfo(getAccountPublicKey(account), txOptions),
tx.getPaymentInfo(
account.txCreator ?? getFakeTxCreator(getAccountPublicKey(account)),
txOptions,
),
),
catchError((ex) => {
console.error(ex)
@@ -69,7 +69,7 @@ const customExtensionsCount$ = state(
)
// powers of 2 from 4 to 16 (incl)
const periodOptions = new Array(16 - 4 + 1).fill(0).map((_, i) => 1 << (i + 4))
const periodOptions = new Array(12 - 4 + 1).fill(0).map((_, i) => 1 << (i + 4))
const chainNonce$ = unsafeApi$.pipeState(
switchMapSuspended((api) =>
@@ -105,11 +105,11 @@ export const SubmitExtrinsic = forwardRef<HTMLElement>((_, ref) => {
const [isSigning, setIsSigning] = useState(false)
const signAndSubmit = async () => {
if (!account?.signer || !tx) return
if (!account?.txCreator || !tx) return
setIsSigning(true)
try {
const signedExtrinsic = await tx.sign(account.signer, txOptions)
const signedExtrinsic = await tx.create(account.txCreator, txOptions)
trackTx(signedExtrinsic, tx.decodedCall, account)
} catch (ex) {
console.error(ex)
@@ -223,7 +223,7 @@ export const SubmitExtrinsic = forwardRef<HTMLElement>((_, ref) => {
<ActionButton
className="flex w-full items-center justify-center gap-2 rounded-md py-2.5 text-sm font-semibold"
disabled={!tx || !account?.signer || isSigning}
disabled={!tx || !account?.txCreator || isSigning}
onClick={signAndSubmit}
>
<Send className="h-4 w-4" />
@@ -23,7 +23,7 @@ export const SelectAccount: FC<{
const groups = Object.entries(availableAccounts)
.map(
([group, accounts]) =>
[group, accounts.filter((acc) => acc.signer)] as const,
[group, accounts.filter((acc) => acc.txCreator)] as const,
)
.filter(([, accounts]) => accounts.length > 0)
.map(([key, accounts]) => ({
+17 -4
View File
@@ -6,10 +6,23 @@ import {
mergeWithKey,
switchMapSuspended,
} from "@react-rxjs/utils"
import { TxOptions } from "polkadot-api"
import { combineLatest, distinctUntilChanged, map, scan } from "rxjs"
import { callData$ } from "../componentValue.state"
import { customSignedExtensions$ } from "../CustomSignedExt"
import { ArgsForCreator } from "@polkadot-api/tx-creator"
import type { CommonSignerTxCreator } from "@polkadot-api/signers-common"
type CommonOpts = ArgsForCreator<CommonSignerTxCreator, any>
type CustomSignedExtensions = Record<
string,
{
value?: Uint8Array
additionalSigned?: Uint8Array
}
>
type TxOptions = CommonOpts & {
customSignedExtensions?: CustomSignedExtensions
}
export const [nonceChanged$, setNonce] = createSignal<string>()
export const [nonceBlurred$, blurNonce] = createSignal()
@@ -29,7 +42,7 @@ export const nonce$ = state(
"",
)
type Mortality = NonNullable<TxOptions<any, any>["mortality"]>
type Mortality = NonNullable<TxOptions["mortality"]>
export const DEFAULT_MORTAL = {
mortal: true,
period: 64,
@@ -59,7 +72,7 @@ export const txOptions$ = state(
tip$.pipe(map((v) => (isIntegerStr(v) ? BigInt(v) : null))),
customSignedExtensions$,
]).pipe(
map(([nonce, mortality, tip, signedExt]): TxOptions<any, any> => {
map(([nonce, mortality, tip, signedExt]): Omit<TxOptions, "asset"> => {
return {
mortality,
nonce: nonce ?? undefined,
@@ -68,5 +81,5 @@ export const txOptions$ = state(
}
}),
),
{} satisfies TxOptions<any, any>,
{},
)
+4 -18
View File
@@ -9,7 +9,6 @@ import Worker from "@polkadot-api/forklift/worker?worker"
import { ChainHead$ } from "@polkadot-api/observable-client"
import { shareLatest } from "@react-rxjs/core"
import {
AccountId,
Binary,
createClient,
Enum,
@@ -18,8 +17,7 @@ import {
PolkadotClient,
Transaction,
} from "polkadot-api"
import { getPolkadotSigner } from "polkadot-api/signer"
import { AccountAddress } from "polkahub"
import { getFakeTxCreator } from "polkadot-api/tx-creator"
import {
catchError,
combineLatest,
@@ -75,7 +73,7 @@ const forkLift$ = chainClient$.pipe(
const fakeSign$ = (tx: Transaction, txOptions: any) =>
combineLatest({
signer: selectedAccount$.pipe(
map((account) => (account ? createFakeSigner(account.address) : null)),
map((account) => (account ? getFakeTxCreator(account.address) : null)),
),
mockChain: forkLift$.pipe(
switchMap(async ({ forkClient }) => {
@@ -91,7 +89,7 @@ const fakeSign$ = (tx: Transaction, txOptions: any) =>
switchMap(async ({ signer, mockChain: { tx, unsafeApi, client } }) => {
if (!signer) return null
const extrinsic = await tx.sign(signer, txOptions)
const extrinsic = await tx.create(signer, txOptions)
return { extrinsic, tx, unsafeApi, client }
}),
@@ -148,7 +146,7 @@ export const dryRun$ = (tx: Transaction, txOptions: any) =>
value: undefined,
}
}
if (evt.type === "txBestBlocksState" && evt.found) {
if (evt.type === "inBestBlock") {
return {
type: "valid" as const,
value: {
@@ -306,15 +304,3 @@ const createTmpForklift = async (
forkliftWorker,
] as const
}
const decodeAddress = (address: AccountAddress) =>
address.startsWith("0x") ? Binary.fromHex(address) : AccountId().enc(address)!
export const createFakeSigner = (address: AccountAddress) =>
getPolkadotSigner(decodeAddress(address), "Sr25519", () => {
// From https://wiki.acala.network/build/sdks/homa
const signature = new Uint8Array(64)
signature.fill(0xcd)
signature.set([0xde, 0xad, 0xbe, 0xef])
return signature
})
+2 -2
View File
@@ -40,14 +40,14 @@ export const Extrinsic: FC<{ extrinsic: ExtrinsicDescriptor }> = ({
<div>
<h4>Signed Extensions</h4>
{/* TODO: adapt for different extrinsic versions */}
<SignedExtensions signedExtensions={extrinsic.signedExtensions[0]} />
<SignedExtensions signedExtensions={extrinsic.extensionsByVersion[0]} />
</div>
</div>
)
}
type SignedExtension =
ExtrinsicDescriptor["signedExtensions"][number] extends Array<infer R>
ExtrinsicDescriptor["extensionsByVersion"][number] extends Array<infer R>
? R
: never
const SignedExtensions: FC<{ signedExtensions: Array<SignedExtension> }> = ({
+3 -3
View File
@@ -282,8 +282,8 @@ const ExportTx: FC<{
const [account] = useSelectedAccount()
const submit = async () => {
if (!account?.signer) return
const signed = await tx.sign(account.signer)
if (!account?.txCreator) return
const signed = await tx.create(account.txCreator)
trackTx(signed, tx.decodedCall, account)
}
@@ -292,7 +292,7 @@ const ExportTx: FC<{
{number != null ? <div>#{number}</div> : null}
<Button
type="button"
disabled={!account?.signer}
disabled={!account?.txCreator}
onClick={submit}
className="flex h-10 w-full items-center justify-center gap-2 rounded-md text-sm font-semibold"
>
+6 -6
View File
@@ -216,8 +216,8 @@ export const toggleExtension = async (id: string) => {
}
export const getAccountPublicKey = (account: Account) =>
account.signer
? account.signer.publicKey
account.txCreator && "publicKey" in account.txCreator
? (account.txCreator.publicKey as Uint8Array)
: account.address.startsWith("0x")
? Binary.fromHex(account.address)
: AccountId().enc(account.address)
@@ -225,9 +225,9 @@ export const getAccountPublicKey = (account: Account) =>
// 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.txCreator && "publicKey" in account.txCreator
? Binary.toHex(account.txCreator.publicKey as Uint8Array)
: account.address
: account.signer
? AccountId().dec(account.signer.publicKey)
: account.txCreator && "publicKey" in account.txCreator
? AccountId().dec(account.txCreator.publicKey as Uint8Array)
: account.address