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
parent 040dae8920
commit 2dec1477a8
21 changed files with 2263 additions and 2542 deletions

View File

@@ -1,5 +1,5 @@
{
"version": "0.1.0-autogenerated.5569951721440591425",
"version": "0.1.0-autogenerated.1970128548420940830",
"name": "@polkadot-api/descriptors",
"files": [
"dist"

Binary file not shown.

Binary file not shown.

View File

@@ -6,13 +6,13 @@
"chain": "polkadot_people",
"metadata": ".papi/metadata/polkadot_people.scale",
"genesis": "0x67fa177a097bfa18f77ea95ab56e9bcdfeb0e5b8a40e46298bb93e16b6fc5008",
"codeHash": "0x4f2b23add794f311aa80b487b3cca971fe4bdc1850ebd5d96a693352f49743cb"
"codeHash": "0x931bec645b70c064a449717598861bf3b75fc1499d3650c43889d3b4a5d33853"
},
"dotAh": {
"chain": "polkadot_asset_hub",
"metadata": ".papi/metadata/dotAh.scale",
"genesis": "0x68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f",
"codeHash": "0xd8c8c132cd1f51504af2cd45c8dd7c5565dff0a4e9d7fde72daa8975c58ef961"
"codeHash": "0x80019b852cb8dbd47f5c13dc9a56eb5a30b9d68440d681e9959ecd25f3751de9"
}
}
}

View File

@@ -5,7 +5,7 @@ import reactRefresh from "eslint-plugin-react-refresh"
import tseslint from "typescript-eslint"
export default tseslint.config(
{ ignores: ["dist"] },
{ ignores: ["dist", ".papi"] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ["**/*.{ts,tsx}"],
@@ -19,6 +19,9 @@ export default tseslint.config(
},
rules: {
...reactHooks.configs.recommended.rules,
"react-hooks/immutability": "off",
"react-hooks/refs": "off",
"react-hooks/set-state-in-effect": "off",
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },

View File

@@ -14,81 +14,73 @@
"semi": false
},
"dependencies": {
"@acala-network/chopsticks-core": "^1.5.0",
"@acala-network/chopsticks-core": "^1.5.1",
"@ledgerhq/hw-transport-webusb": "^6.35.0",
"@monaco-editor/react": "^4.7.0",
"@paraspell/sdk": "^14.0.0",
"@paraspell/sdk": "14.1.0-rc.1",
"@polkadot-api/descriptors": "file:.papi/descriptors",
"@polkadot-api/forklift": "^0.4.1",
"@polkadot-api/forklift": "^0.5.1",
"@polkadot-api/json-rpc-provider-proxy": "^0.4.1",
"@polkadot-api/metadata-builders": "^0.14.3",
"@polkadot-api/observable-client": "^0.18.7",
"@polkadot-api/react-builder": "0.5.3",
"@polkadot-api/metadata-builders": "^0.15.0",
"@polkadot-api/observable-client": "^0.19.0",
"@polkadot-api/react-builder": "^0.6.0",
"@polkadot-api/react-components": "^0.4.2",
"@polkadot-api/substrate-bindings": "^0.20.3",
"@polkadot-api/tx-utils": "^0.5.0",
"@polkahub/ui-components": "^0.7.1",
"@radix-ui/react-accordion": "^1.2.17",
"@radix-ui/react-dialog": "^1.1.20",
"@radix-ui/react-label": "^2.1.12",
"@radix-ui/react-popover": "^1.1.20",
"@radix-ui/react-radio-group": "^1.4.4",
"@radix-ui/react-scroll-area": "^1.2.15",
"@radix-ui/react-select": "^2.3.4",
"@radix-ui/react-slot": "^1.3.0",
"@radix-ui/react-tabs": "^1.1.18",
"@radix-ui/react-toggle": "^1.1.15",
"@radix-ui/react-toggle-group": "^1.1.16",
"@radix-ui/react-tooltip": "^1.2.13",
"@polkadot-api/signers-common": "^0.3.0",
"@polkadot-api/substrate-bindings": "^0.21.0",
"@polkadot-api/tx-creator": "^0.2.0",
"@polkadot-api/tx-utils": "^0.6.0",
"@polkahub/ui-components": "^0.9.1",
"@radix-ui/react-accordion": "^1.2.20",
"@radix-ui/react-dialog": "^1.1.23",
"@radix-ui/react-label": "^2.1.15",
"@radix-ui/react-popover": "^1.1.23",
"@radix-ui/react-radio-group": "^1.4.7",
"@radix-ui/react-scroll-area": "^1.2.18",
"@radix-ui/react-select": "^2.3.7",
"@radix-ui/react-slot": "^1.3.3",
"@radix-ui/react-tabs": "^1.1.21",
"@radix-ui/react-toggle": "^1.1.18",
"@radix-ui/react-toggle-group": "^1.1.19",
"@radix-ui/react-tooltip": "^1.2.16",
"@react-rxjs/core": "^0.10.8",
"@react-rxjs/utils": "^0.9.7",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"idb-keyval": "^6.3.0",
"lucide-react": "^1.25.0",
"polkadot-api": "^2.2.1",
"polkahub": "^0.7.1",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"lucide-react": "^1.33.0",
"polkadot-api": "^3.0.0",
"polkahub": "^0.9.1",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"react-portal": "^4.3.0",
"react-router-dom": "^7.18.1",
"react-svg": "^17.2.4",
"react-virtuoso": "^4.18.11",
"react-router-dom": "^7.18.2",
"react-svg": "^19.1.4",
"react-virtuoso": "^4.18.12",
"react18-json-view": "^0.2.10",
"rxjs": "^7.8.2",
"save-as": "^0.1.8",
"tailwind-merge": "^3.6.0",
"tailwindcss-animate": "^1.0.7",
"uplot": "^1.6.32",
"uuid": "^14.0.1"
"uuid": "^14.0.2"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@tailwindcss/vite": "^4.3.3",
"@types/node": "^26.1.1",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@types/node": "^26.2.0",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4",
"@types/react-portal": "^4.0.7",
"@vitejs/plugin-react": "^6.0.3",
"eslint": "^10.7.0",
"@vitejs/plugin-react": "^6.1.0",
"eslint": "^10.9.0",
"eslint-plugin-react-hooks": "7.1.1",
"eslint-plugin-react-refresh": "^0.5.3",
"globals": "^17.7.0",
"eslint-plugin-react-refresh": "^0.5.4",
"globals": "^17.11.0",
"prettier": "^3.9.6",
"tailwindcss": "^4.3.3",
"typescript": "^7.0.2",
"typescript-eslint": "^8.65.0",
"vite": "^8.1.5"
"typescript": "6",
"typescript-eslint": "^8.67.0",
"vite": "^8.2.2"
},
"packageManager": "pnpm@10.34.5+sha512.a4ee05f2f73658255bd6a89859c065a45c28a57daefae2c893a168ee2b73168c37b91e83e57ea67654ad03f03031746430e8bce38e362e042605fb8abc80192e",
"pnpm": {
"patchedDependencies": {
"react18-json-view": "patches/react18-json-view.patch"
},
"onlyBuiltDependencies": [
"@reown/appkit",
"bufferutil",
"utf-8-validate"
]
}
"packageManager": "pnpm@11.22.0+sha512.1ff870c4c6133dfd88fb2afc46dd13d47f09c9794b438c6fdb47ca98caf3bc16381ee0be93a091b8e3824cf01f889f46d7d9e20910fb0be1ab0fb5baa80dd621"
}

4562
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

11
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,11 @@
allowBuilds:
"@reown/appkit": true
esbuild: true
patchedDependencies:
react18-json-view: patches/react18-json-view.patch
# TODO: remove when ParaSpell uses PAPI v3 stable
overrides:
"@paraspell/sdk@14.1.0-rc.1>polkadot-api": "3.0.0"
"@paraspell/sdk@14.1.0-rc.1>@polkadot-api/signers-common": "0.3.0"
"@paraspell/sdk@14.1.0-rc.1>@polkadot-api/substrate-bindings": "0.21.0"
"@paraspell/descriptors@14.1.0-rc.1>polkadot-api": "3.0.0"

View File

@@ -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<

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"
>

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$,
),

View File

@@ -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":

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)

View File

@@ -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" />

View File

@@ -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]) => ({

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>,
{},
)

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
})

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> }> = ({

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"
>

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

View File

@@ -6,8 +6,9 @@ import tailwindcss from "@tailwindcss/vite"
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
dedupe: ["react", "react-dom"],
alias: {
"@": path.resolve(__dirname, "./src"),
"@": path.resolve(import.meta.dirname, "./src"),
},
},
plugins: [react(), tailwindcss()],