diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e44a58047..3e6ecf574 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -313,9 +313,6 @@ importers: '@polkadot-api/observable-client': specifier: ^0.3.1 version: 0.3.1(@polkadot-api/substrate-client@0.1.4)(rxjs@7.8.1) - '@polkadot-api/polkadot-signer': - specifier: ~0.0.2 - version: 0.0.2 '@polkadot-api/signer': specifier: ~0.0.2 version: 0.0.2 @@ -342,8 +339,11 @@ importers: version: 2.0.29 devDependencies: '@polkadot-api/json-rpc-provider': - specifier: ^0.0.1 + specifier: ~0.0.1 version: 0.0.1 + '@polkadot-api/polkadot-signer': + specifier: ~0.0.2 + version: 0.0.2 '@types/chrome': specifier: ^0.0.268 version: 0.0.268 @@ -3634,7 +3634,6 @@ packages: /@noble/hashes@1.4.0: resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} engines: {node: '>= 16'} - dev: false /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -3784,7 +3783,6 @@ packages: dependencies: '@polkadot-api/substrate-bindings': 0.6.0 '@polkadot-api/utils': 0.1.0 - dev: false /@polkadot-api/observable-client@0.3.1(@polkadot-api/substrate-client@0.1.4)(rxjs@7.8.1): resolution: {integrity: sha512-ioun8PfDwmr2nRCIFPS3X7qMaJxeyotHT8vep0VNVYU13dUIBojaHQCpe9l4jN8MXoG63h1wAVT9dshGSiTDQw==} @@ -3814,7 +3812,6 @@ packages: '@polkadot-api/metadata-builders': 0.3.1 '@polkadot-api/substrate-bindings': 0.6.0 '@polkadot-api/utils': 0.1.0 - dev: false /@polkadot-api/signer@0.0.2: resolution: {integrity: sha512-nWpzsU8KA27jrS1G6gV3yK4s9LxOsHbySFr3GpCQT0Iz61VHPQ9YLqnkR+J4I7myrHijiRchbMlN/iF08EbAQQ==} @@ -3868,7 +3865,6 @@ packages: '@polkadot-api/utils': 0.1.0 '@scure/base': 1.1.6 scale-ts: 1.6.0 - dev: false /@polkadot-api/substrate-client@0.1.4: resolution: {integrity: sha512-MljrPobN0ZWTpn++da9vOvt+Ex+NlqTlr/XT7zi9sqPtDJiQcYl+d29hFAgpaeTqbeQKZwz3WDE9xcEfLE8c5A==} @@ -6472,7 +6468,6 @@ packages: /@scure/base@1.1.6: resolution: {integrity: sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==} - dev: false /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -13870,7 +13865,6 @@ packages: /scale-ts@1.6.0: resolution: {integrity: sha512-Ja5VCjNZR8TGKhUumy9clVVxcDpM+YFjAnkMuwQy68Hixio3VRRvWdE3g8T/yC+HXA0ZDQl2TGyUmtmbcVl40Q==} - dev: false /scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} diff --git a/projects/wallet-template/src/background/createBackgroundRpc.ts b/projects/wallet-template/src/background/createBackgroundRpc.ts index 52ef2ebbf..0299209e1 100644 --- a/projects/wallet-template/src/background/createBackgroundRpc.ts +++ b/projects/wallet-template/src/background/createBackgroundRpc.ts @@ -5,6 +5,7 @@ import { createRpc, RpcError, } from "@substrate/light-client-extension-helpers/utils" +import { createTx } from "@substrate/light-client-extension-helpers/tx-helper" import { ss58Address, ss58Decode } from "@polkadot-labs/hdkd-helpers" import { toHex, fromHex } from "@polkadot-api/utils" import { getPolkadotSigner } from "@polkadot-api/signer" @@ -15,7 +16,6 @@ import { UserSignedExtensionName } from "../types/UserSignedExtension" import { createClient } from "@polkadot-api/substrate-client" import { getObservableClient } from "@polkadot-api/observable-client" import { filter, firstValueFrom, map, mergeMap, take } from "rxjs" -import { createTx } from "./tx-helper/create-tx" import * as pjs from "./pjs" import { Bytes, Variant } from "@polkadot-api/substrate-bindings" import { InPageRpcSpec } from "../inpage/types" @@ -86,7 +86,7 @@ export const createBackgroundRpc = ( const client = getObservableClient(createClient(chain.provider)) const chainHead$ = client.chainHead$() - const { best: atBlock, userSignedExtensionNames } = await firstValueFrom( + const userSignedExtensionNames = await firstValueFrom( chainHead$.best$.pipe( mergeMap((blockInfo) => chainHead$.getRuntimeContext$(blockInfo.hash).pipe( @@ -96,10 +96,6 @@ export const createBackgroundRpc = ( .map(({ identifier }) => identifier) .filter(isUserSignedExtensionName), ), - map((userSignedExtensionNames) => ({ - best: blockInfo, - userSignedExtensionNames, - })), ), ), filter(Boolean), @@ -166,13 +162,15 @@ export const createBackgroundRpc = ( ? userSignedExtensions.ChargeAssetTxPayment?.tip : userSignedExtensions.ChargeTransactionPayment) ?? 0n - const tx = await firstValueFrom( - createTx(chainHead$, signer, fromHex(callData), atBlock, { + const tx = await createTx(chain.provider)({ + signer, + callData: fromHex(callData), + hinted: { mortality, asset, tip, - }).pipe(filter(Boolean)), - ) + }, + }) return toHex(tx) } finally { diff --git a/projects/wallet-template/src/background/tx-helper/README.md b/projects/wallet-template/src/background/tx-helper/README.md deleted file mode 100644 index 432086ec4..000000000 --- a/projects/wallet-template/src/background/tx-helper/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# tx-helper - -This is a copy and paste of the [Polkadot API Client Tx directory](https://github.com/polkadot-api/polkadot-api/tree/df5c969a1c73fa381aaf71f72e8c52e64c788b36/packages/client/src/tx). We use this in substrate connect to create transactions. - -These are considered internal to polkadot API which is why we need to copy and paste. - -see: https://github.com/polkadot-api/polkadot-api/issues/417 diff --git a/projects/wallet-template/src/background/tx-helper/create-tx.ts b/projects/wallet-template/src/background/tx-helper/create-tx.ts deleted file mode 100644 index 33d0575ce..000000000 --- a/projects/wallet-template/src/background/tx-helper/create-tx.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { Observable, combineLatest, mergeMap, of, take } from "rxjs" -import { BlockInfo, ChainHead$ } from "@polkadot-api/observable-client" -import { - ChargeAssetTxPayment, - ChargeTransactionPayment, - CheckMortality, -} from "./signed-extensions/user" -import * as chainSignedExtensions from "./signed-extensions/chain" -import type { PolkadotSigner } from "@polkadot-api/polkadot-signer" -import { _void } from "@polkadot-api/substrate-bindings" -import { empty } from "./signed-extensions/utils" - -type HintedSignedExtensions = Partial<{ - tip: bigint - mortality: { mortal: false } | { mortal: true; period: number } - asset: Uint8Array - nonce: number -}> - -export const createTx: ( - chainHead: ChainHead$, - signer: PolkadotSigner, - callData: Uint8Array, - atBlock: BlockInfo, - hinted?: HintedSignedExtensions, -) => Observable = ( - chainHead, - signer, - callData, - atBlock, - hinted = {}, -) => - chainHead.getRuntimeContext$(atBlock.hash).pipe( - take(1), - mergeMap((ctx) => { - const signedExtensionsCtx = { - metadata: ctx.metadata, - chainHead: chainHead, - callData: callData, - at: atBlock.hash, - from: signer.publicKey, - } - - const mortality: Parameters[0] = !hinted.mortality - ? { period: 64, blockNumber: atBlock.number } - : hinted.mortality.mortal - ? { period: hinted.mortality.period, blockNumber: atBlock.number } - : undefined // immortal - - return combineLatest( - ctx.metadata.extrinsic.signedExtensions.map( - ({ identifier, type, additionalSigned }) => { - if (identifier === "CheckMortality") - return CheckMortality(mortality, signedExtensionsCtx) - - if (identifier === "ChargeTransactionPayment") - return ChargeTransactionPayment(hinted.tip ?? 0n) - - if (identifier === "ChargeAssetTxPayment") - return ChargeAssetTxPayment(hinted.tip ?? 0n, hinted.asset) - - if (identifier === "CheckNonce" && "nonce" in hinted) - return chainSignedExtensions.getNonce(hinted.nonce!) - - const fn = chainSignedExtensions[identifier as "CheckGenesis"] - if (!fn) { - if ( - ctx.dynamicBuilder.buildDefinition(type) === _void && - ctx.dynamicBuilder.buildDefinition(additionalSigned) === _void - ) - return of({ - value: empty, - additionalSigned: empty, - }) - - throw new Error(`Unsupported signed-extension: ${identifier}`) - } - return fn(signedExtensionsCtx) - }, - ), - ).pipe( - mergeMap((signedExtensions) => - signer.sign( - callData, - Object.fromEntries( - ctx.metadata.extrinsic.signedExtensions.map( - ({ identifier }, idx) => [ - identifier, - { identifier, ...signedExtensions[idx] }, - ], - ), - ), - ctx.metadataRaw, - atBlock.number, - ), - ), - ) - }), - ) diff --git a/projects/wallet-template/src/background/tx-helper/descriptors.ts b/projects/wallet-template/src/background/tx-helper/descriptors.ts deleted file mode 100644 index 6b9adeb77..000000000 --- a/projects/wallet-template/src/background/tx-helper/descriptors.ts +++ /dev/null @@ -1,101 +0,0 @@ -import type { DescriptorValues } from "@polkadot-api/codegen" - -export type PlainDescriptor = { _type?: T } -export type AssetDescriptor = string & { _type?: T } -export type StorageDescriptor< - Args extends Array, - T, - Optional extends true | false, -> = { _type: T; _args: Args; _optional: Optional } - -export type TxDescriptor = { - ___: Args -} - -export type RuntimeDescriptor, T> = [Args, T] - -// pallet -> name -> descriptor -export type DescriptorEntry = Record> - -export type PalletsTypedef< - St extends DescriptorEntry>, - Tx extends DescriptorEntry>, - Ev extends DescriptorEntry>, - Err extends DescriptorEntry>, - Ct extends DescriptorEntry>, -> = { - __storage: St - __tx: Tx - __event: Ev - __error: Err - __const: Ct -} - -export type ApisTypedef< - T extends DescriptorEntry>, -> = T - -export { DescriptorValues } - -export type ChainDefinition = { - descriptors: Promise & { - pallets: PalletsTypedef - apis: ApisTypedef - } - asset: AssetDescriptor - checksums: Promise -} - -type ExtractStorage< - T extends DescriptorEntry>, -> = { - [K in keyof T]: { - [KK in keyof T[K]]: T[K][KK] extends StorageDescriptor< - infer Key, - infer Value, - infer Optional - > - ? { - KeyArgs: Key - Value: Value - IsOptional: Optional - } - : unknown - } -} - -type ExtractTx>> = { - [K in keyof T]: { - [KK in keyof T[K]]: T[K][KK] extends TxDescriptor - ? Args - : unknown - } -} - -type ExtractPlain>> = { - [K in keyof T]: { - [KK in keyof T[K]]: T[K][KK] extends PlainDescriptor - ? Value - : unknown - } -} - -export type QueryFromPalletsDef< - T extends PalletsTypedef, -> = ExtractStorage - -export type TxFromPalletsDef< - T extends PalletsTypedef, -> = ExtractTx - -export type EventsFromPalletsDef< - T extends PalletsTypedef, -> = ExtractPlain - -export type ErrorsFromPalletsDef< - T extends PalletsTypedef, -> = ExtractPlain - -export type ConstFromPalletsDef< - T extends PalletsTypedef, -> = ExtractPlain diff --git a/projects/wallet-template/src/background/tx-helper/index.ts b/projects/wallet-template/src/background/tx-helper/index.ts deleted file mode 100644 index b67f145c0..000000000 --- a/projects/wallet-template/src/background/tx-helper/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export type * from "./types" -export { createTxEntry, submit, submit$ } from "./tx" diff --git a/projects/wallet-template/src/background/tx-helper/runtime.ts b/projects/wallet-template/src/background/tx-helper/runtime.ts deleted file mode 100644 index 8ec47de08..000000000 --- a/projects/wallet-template/src/background/tx-helper/runtime.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { - ChainHead$, - RuntimeContext, - getObservableClient, -} from "@polkadot-api/observable-client" -import { - Observable, - ReplaySubject, - combineLatest, - connectable, - filter, - firstValueFrom, - map, -} from "rxjs" -import { DescriptorValues } from "./descriptors" - -export const enum OpType { - Storage = "storage", - Tx = "tx", - Event = "events", - Error = "errors", - Const = "constants", -} - -export class Runtime { - private constructor( - private _ctx: RuntimeContext, - private _checksums: string[], - private _descriptors: DescriptorValues, - ) {} - - /** - * @access package - Internal implementation detail. Do not use. - */ - static _create( - ctx: RuntimeContext, - checksums: string[], - descriptors: DescriptorValues, - ) { - return new Runtime(ctx, checksums, descriptors) - } - - /** - * @access package - Internal implementation detail. Do not use. - */ - _getCtx() { - return this._ctx - } - - /** - * @access package - Internal implementation detail. Do not use. - */ - _getPalletChecksum(opType: OpType, pallet: string, name: string) { - return this._checksums[this._descriptors[opType][pallet][name]] - } - - /** - * @access package - Internal implementation detail. Do not use. - */ - _getApiChecksum(name: string, method: string) { - return this._checksums[this._descriptors.apis[name][method]] - } -} - -export type RuntimeApi = Observable & { - /** - * @returns Promise that resolves in the `Runtime` as soon as it's - * loaded. - */ - latest: () => Promise -} - -export const getRuntimeApi = ( - checksums: Promise, - descriptors: Promise, - chainHead: ReturnType["chainHead$"]>, -): RuntimeApi => { - const runtimeWithChecksums$ = connectable( - combineLatest([chainHead.runtime$, checksums, descriptors]).pipe( - map(([x, checksums, descriptors]) => - x ? Runtime._create(x, checksums, descriptors) : null, - ), - ), - { - connector: () => new ReplaySubject(1), - }, - ) - runtimeWithChecksums$.connect() - - const result = runtimeWithChecksums$.pipe( - filter((v) => Boolean(v)), - ) as RuntimeApi - result.latest = () => firstValueFrom(result) - - return result -} - -export interface IsCompatible { - /** - * `isCompatible` enables you to check whether or not the call you're trying - * to make is compatible with the descriptors you generated on dev time. - * In this case the function waits for `Runtime` to load, and returns - * asynchronously. - * - * @returns Promise that resolves with the result of the compatibility - * check. - */ - (): Promise - /** - * Passing the runtime makes the function to return synchronously. - * - * @returns Result of the compatibility check. - */ - (runtime: Runtime): boolean -} - -export const compatibilityHelper = - ( - runtimeApi: RuntimeApi, - getDescriptorChecksum: (runtime: Runtime) => string, - ) => - (getChecksum: (ctx: RuntimeContext) => string | null) => { - function isCompatibleSync(runtime: Runtime) { - return getChecksum(runtime._getCtx()) === getDescriptorChecksum(runtime) - } - - const isCompatible: IsCompatible = (runtime?: Runtime): any => { - if (runtime) { - return isCompatibleSync(runtime) - } - - return runtimeApi.latest().then(isCompatibleSync) - } - const waitChecksums = async () => { - const runtime = await runtimeApi.latest() - return (ctx: RuntimeContext) => - getChecksum(ctx) === getDescriptorChecksum(runtime) - } - const compatibleRuntime$ = ( - chainHead: ChainHead$, - hash: string | null, - error: () => Error, - ) => - combineLatest([chainHead.getRuntimeContext$(hash), waitChecksums()]).pipe( - map(([ctx, isCompatible]) => { - if (!isCompatible(ctx)) { - throw error() - } - return ctx - }), - ) - - const withCompatibleRuntime = - ( - chainHead: ChainHead$, - mapper: (x: T) => string, - error: () => Error, - ) => - (source$: Observable): Observable<[T, RuntimeContext]> => - combineLatest([ - source$.pipe(chainHead.withRuntime(mapper)), - waitChecksums(), - ]).pipe( - map(([[x, ctx], isCompatible]) => { - if (!isCompatible(ctx)) { - throw error() - } - return [x, ctx] - }), - ) - - return { - isCompatible, - waitChecksums, - withCompatibleRuntime, - compatibleRuntime$, - } - } -export type CompatibilityHelper = ReturnType diff --git a/projects/wallet-template/src/background/tx-helper/signed-extensions/chain/CheckGenesis.ts b/projects/wallet-template/src/background/tx-helper/signed-extensions/chain/CheckGenesis.ts deleted file mode 100644 index b879e94fd..000000000 --- a/projects/wallet-template/src/background/tx-helper/signed-extensions/chain/CheckGenesis.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { map } from "rxjs" -import type { GetChainSignedExtension } from "../internal-types" -import { empty, genesisHashFromCtx } from "../utils" - -export const CheckGenesis: GetChainSignedExtension = (ctx) => - genesisHashFromCtx(ctx).pipe( - map((additionalSigned) => ({ value: empty, additionalSigned })), - ) diff --git a/projects/wallet-template/src/background/tx-helper/signed-extensions/chain/CheckMetadataHash.ts b/projects/wallet-template/src/background/tx-helper/signed-extensions/chain/CheckMetadataHash.ts deleted file mode 100644 index 9211eff37..000000000 --- a/projects/wallet-template/src/background/tx-helper/signed-extensions/chain/CheckMetadataHash.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { of } from "rxjs" -import type { GetChainSignedExtension } from "../internal-types" - -export const CheckMetadataHash: GetChainSignedExtension = () => - of({ - value: Uint8Array.from([0]), - additionalSigned: Uint8Array.from([0]), - }) diff --git a/projects/wallet-template/src/background/tx-helper/signed-extensions/chain/CheckNonce.ts b/projects/wallet-template/src/background/tx-helper/signed-extensions/chain/CheckNonce.ts deleted file mode 100644 index d1e690185..000000000 --- a/projects/wallet-template/src/background/tx-helper/signed-extensions/chain/CheckNonce.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { mergeMap, of } from "rxjs" -import { compact, u16, u32, u64, u8 } from "@polkadot-api/substrate-bindings" -import type { - GetChainSignedExtension, - SignedExtension, -} from "../internal-types" -import { empty } from "../utils" -import { fromHex, toHex } from "@polkadot-api/utils" - -const NONCE_RUNTIME_CALL = "AccountNonceApi_account_nonce" -const lenToDecoder = { - 1: u8.dec, - 2: u16.dec, - 4: u32.dec, - 8: u64.dec, -} - -export const getNonce = (input: number | bigint): SignedExtension => - of({ value: compact.enc(input), additionalSigned: empty }) - -export const CheckNonce: GetChainSignedExtension = (ctx) => - ctx.chainHead.call$(ctx.at, NONCE_RUNTIME_CALL, toHex(ctx.from)).pipe( - mergeMap((result) => { - const bytes = fromHex(result) - const decoder = lenToDecoder[bytes.length as 2 | 4 | 8] - if (!decoder) - throw new Error(`${NONCE_RUNTIME_CALL} retrieved wrong data`) - return getNonce(decoder(bytes)) - }), - ) diff --git a/projects/wallet-template/src/background/tx-helper/signed-extensions/chain/CheckSpecVersion.ts b/projects/wallet-template/src/background/tx-helper/signed-extensions/chain/CheckSpecVersion.ts deleted file mode 100644 index 8322114e6..000000000 --- a/projects/wallet-template/src/background/tx-helper/signed-extensions/chain/CheckSpecVersion.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { map } from "rxjs" -import type { GetChainSignedExtension } from "../internal-types" -import { empty, systemVersionProp$ } from "../utils" - -export const CheckSpecVersion: GetChainSignedExtension = ({ metadata }) => - systemVersionProp$("spec_version", metadata).pipe( - map((additionalSigned) => ({ additionalSigned, value: empty })), - ) diff --git a/projects/wallet-template/src/background/tx-helper/signed-extensions/chain/CheckTxVersion.ts b/projects/wallet-template/src/background/tx-helper/signed-extensions/chain/CheckTxVersion.ts deleted file mode 100644 index b69a16a25..000000000 --- a/projects/wallet-template/src/background/tx-helper/signed-extensions/chain/CheckTxVersion.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { map } from "rxjs" -import type { GetChainSignedExtension } from "../internal-types" -import { empty, systemVersionProp$ } from "../utils" - -export const CheckTxVersion: GetChainSignedExtension = ({ metadata }) => - systemVersionProp$("transaction_version", metadata).pipe( - map((additionalSigned) => ({ additionalSigned, value: empty })), - ) diff --git a/projects/wallet-template/src/background/tx-helper/signed-extensions/chain/index.ts b/projects/wallet-template/src/background/tx-helper/signed-extensions/chain/index.ts deleted file mode 100644 index be6e8ba5c..000000000 --- a/projects/wallet-template/src/background/tx-helper/signed-extensions/chain/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./CheckGenesis" -export * from "./CheckMetadataHash" -export * from "./CheckNonce" -export * from "./CheckSpecVersion" -export * from "./CheckTxVersion" diff --git a/projects/wallet-template/src/background/tx-helper/signed-extensions/index.ts b/projects/wallet-template/src/background/tx-helper/signed-extensions/index.ts deleted file mode 100644 index 6fc3bbfdf..000000000 --- a/projects/wallet-template/src/background/tx-helper/signed-extensions/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * as chainSignedExtensions from "./chain" -export * as userSignedExtensions from "./user" diff --git a/projects/wallet-template/src/background/tx-helper/signed-extensions/internal-types.ts b/projects/wallet-template/src/background/tx-helper/signed-extensions/internal-types.ts deleted file mode 100644 index 66fc1c983..000000000 --- a/projects/wallet-template/src/background/tx-helper/signed-extensions/internal-types.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { V14, V15 } from "@polkadot-api/substrate-bindings" -import { Observable } from "rxjs" -import { getObservableClient } from "@polkadot-api/observable-client" - -export interface ChainExtensionCtx { - callData: Uint8Array - from: Uint8Array - metadata: V14 | V15 - at: string - chainHead: ReturnType["chainHead$"]> -} - -export type SignedExtension = Observable<{ - value: Uint8Array - additionalSigned: Uint8Array -}> - -export type GetChainSignedExtension = ( - ctx: ChainExtensionCtx, -) => SignedExtension diff --git a/projects/wallet-template/src/background/tx-helper/signed-extensions/user/ChargeAssetTxPayment.ts b/projects/wallet-template/src/background/tx-helper/signed-extensions/user/ChargeAssetTxPayment.ts deleted file mode 100644 index 5103a5d84..000000000 --- a/projects/wallet-template/src/background/tx-helper/signed-extensions/user/ChargeAssetTxPayment.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { of } from "rxjs" -import { - Bytes, - Option, - Struct, - compact, -} from "@polkadot-api/substrate-bindings" -import { empty } from "../utils" -import { SignedExtension } from "../internal-types" - -const encoder = Struct({ - tip: compact, - asset: Option(Bytes(Infinity)), -}).enc - -export const ChargeAssetTxPayment = ( - tip: number | bigint, - asset: Uint8Array | undefined, -): SignedExtension => - of({ - value: encoder({ - tip, - asset, - }), - additionalSigned: empty, - }) diff --git a/projects/wallet-template/src/background/tx-helper/signed-extensions/user/ChargeTransactionPayment.ts b/projects/wallet-template/src/background/tx-helper/signed-extensions/user/ChargeTransactionPayment.ts deleted file mode 100644 index a1233895a..000000000 --- a/projects/wallet-template/src/background/tx-helper/signed-extensions/user/ChargeTransactionPayment.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { compactBn } from "@polkadot-api/substrate-bindings" -import { of } from "rxjs" -import { empty } from "../utils" -import { SignedExtension } from "../internal-types" - -export const ChargeTransactionPayment = (tip: bigint): SignedExtension => - of({ - value: compactBn.enc(tip), - additionalSigned: empty, - }) diff --git a/projects/wallet-template/src/background/tx-helper/signed-extensions/user/CheckMortality.ts b/projects/wallet-template/src/background/tx-helper/signed-extensions/user/CheckMortality.ts deleted file mode 100644 index 063ed56fb..000000000 --- a/projects/wallet-template/src/background/tx-helper/signed-extensions/user/CheckMortality.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { map, of } from "rxjs" -import { Bytes, enhanceEncoder, u16 } from "@polkadot-api/substrate-bindings" -import { fromHex } from "@polkadot-api/utils" -import { genesisHashFromCtx } from "../utils" -import { ChainExtensionCtx, SignedExtension } from "../internal-types" - -function trailingZeroes(n: number) { - let i = 0 - while (!(n & 1)) { - i++ - n >>= 1 - } - return i -} - -const mortal = enhanceEncoder( - Bytes(2).enc, - (value: { period: number; phase: number }) => { - const factor = Math.max(value.period >> 12, 1) - const left = Math.min(Math.max(trailingZeroes(value.period) - 1, 1), 15) - const right = (value.phase / factor) << 4 - return u16.enc(left | right) - }, -) - -const zero = new Uint8Array([0]) -export const CheckMortality = ( - input: { period: number; blockNumber: number } | undefined, - ctx: ChainExtensionCtx, -): SignedExtension => { - if (!input) - return genesisHashFromCtx(ctx).pipe( - map((additionalSigned) => ({ - additionalSigned, - value: zero, - })), - ) - - const { period, blockNumber } = input - return of({ - additionalSigned: fromHex(ctx.at), - value: mortal({ - period, - phase: blockNumber % period, - }), - }) -} diff --git a/projects/wallet-template/src/background/tx-helper/signed-extensions/user/index.ts b/projects/wallet-template/src/background/tx-helper/signed-extensions/user/index.ts deleted file mode 100644 index f76cb4b18..000000000 --- a/projects/wallet-template/src/background/tx-helper/signed-extensions/user/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./ChargeTransactionPayment" -export * from "./CheckMortality" -export * from "./ChargeAssetTxPayment" diff --git a/projects/wallet-template/src/background/tx-helper/signed-extensions/utils.ts b/projects/wallet-template/src/background/tx-helper/signed-extensions/utils.ts deleted file mode 100644 index 2929cb262..000000000 --- a/projects/wallet-template/src/background/tx-helper/signed-extensions/utils.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { map, noop, of } from "rxjs" -import { - V15, - Storage, - Twox64Concat, - u32, - V14, -} from "@polkadot-api/substrate-bindings" -import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders" -import { fromHex } from "@polkadot-api/utils" -import type { ChainExtensionCtx } from "./internal-types" - -export const empty = new Uint8Array() - -const genesisHashStorageKey = Storage("System")("BlockHash", noop, [ - u32, - Twox64Concat, -]).enc(0) - -export const genesisHashFromCtx = (ctx: ChainExtensionCtx) => - ctx.chainHead - .storage$(ctx.at, "value", () => genesisHashStorageKey, null) - .pipe(map((result) => fromHex(result!))) - -export const systemVersionProp$ = (propName: string, metadata: V14 | V15) => { - const lookupFn = getLookupFn(metadata.lookup) - const dynamicBuilder = getDynamicBuilder(metadata) - - const constant = metadata.pallets - .find((x) => x.name === "System")! - .constants!.find((s) => s.name === "Version")! - - const systemVersion = lookupFn(constant.type) - const systemVersionDec = dynamicBuilder.buildDefinition(constant.type).dec - - if (systemVersion.type !== "struct") throw new Error("not a struct") - - const valueEnc = dynamicBuilder.buildDefinition( - systemVersion.value[propName].id, - ).enc - - return of(valueEnc(systemVersionDec(constant.value)[propName])) -} diff --git a/projects/wallet-template/src/background/tx-helper/submit-fns.ts b/projects/wallet-template/src/background/tx-helper/submit-fns.ts deleted file mode 100644 index b5f37a198..000000000 --- a/projects/wallet-template/src/background/tx-helper/submit-fns.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { Blake2256, HexString } from "@polkadot-api/substrate-bindings" -import { - EMPTY, - Observable, - concat, - distinctUntilChanged, - filter, - lastValueFrom, - map, - mergeMap, - of, - take, -} from "rxjs" -import { - ChainHead$, - PinnedBlocks, - SystemEvent, -} from "@polkadot-api/observable-client" -import { AnalyzedBlock } from "@polkadot-api/observable-client" -import { TxEvent, TxEventsPayload, TxFinalizedPayload } from "./types" -import { continueWith } from "./utils" -import { fromHex, toHex } from "@polkadot-api/utils" - -// TODO: make it dynamic based on the tx-function of the client -const hashFromTx = (tx: HexString) => toHex(Blake2256(fromHex(tx))) - -const computeState = ( - analized$: Observable, - blocks$: Observable, -) => - new Observable< - | { - hash: string - index: number - events: any - } - | boolean - >((observer) => { - const analyzedBlocks = new Map() - let pinnedBlocks: PinnedBlocks - let latestState: - | { - hash: string - index: number - events: any - } - | boolean - - const computeNextState = () => { - let current: string = pinnedBlocks.best - let analyzed: AnalyzedBlock | undefined = analyzedBlocks.get(current) - - while (!analyzed) { - const block = pinnedBlocks.blocks.get(current) - if (!block) break - analyzed = analyzedBlocks.get((current = block.parent)) - } - - if (!analyzed) return // this shouldn't happen, though - - const isFinalized = - pinnedBlocks.blocks.get(analyzed.hash)!.number <= - pinnedBlocks.blocks.get(pinnedBlocks.finalized)!.number - - const found = analyzed.found.type - if ( - found && - typeof latestState === "object" && - latestState.hash === analyzed.hash - ) { - if (isFinalized) observer.complete() - return - } - - observer.next( - (latestState = found - ? { - hash: analyzed.hash, - ...analyzed.found, - } - : analyzed.found.isValid), - ) - - if (isFinalized) { - if (found) observer.complete() - else if (!analyzed.found.isValid) observer.error(new Error("Invalid")) - } - } - - const subscription = blocks$ - .pipe( - distinctUntilChanged( - (a, b) => a.finalized === b.finalized && a.best === b.best, - ), - ) - .subscribe({ - next: (pinned: PinnedBlocks) => { - pinnedBlocks = pinned - if (analyzedBlocks.size === 0) return - computeNextState() - }, - error(e) { - observer.error(e) - }, - }) - - subscription.add( - analized$.subscribe({ - next: (block) => { - analyzedBlocks.set(block.hash, block) - computeNextState() - }, - error(e) { - observer.error(e) - }, - }), - ) - - return subscription - }).pipe(distinctUntilChanged((a, b) => a === b)) - -const getTxSuccessFromSystemEvents = ( - systemEvents: Array, - txIdx: number, -): Omit => { - const events = systemEvents - .filter((x) => x.phase.type === "ApplyExtrinsic" && x.phase.value === txIdx) - .map((x) => x.event) - - const lastEvent = events[events.length - 1] - const ok = - lastEvent.type === "System" && lastEvent.value.type === "ExtrinsicSuccess" - - return { ok, events } -} - -export const submit$ = ( - chainHead: ChainHead$, - broadcastTx$: (tx: string) => Observable, - tx: HexString, - at?: HexString, - emitSign = false, -): Observable => { - const txHash = hashFromTx(tx) - const getTxEvent = < - Type extends TxEvent["type"], - Rest extends Omit, - >( - type: Type, - rest: Rest, - ): TxEvent & { type: Type } => - ({ - type, - txHash, - ...rest, - }) as any - - const at$ = chainHead.pinnedBlocks$.pipe( - take(1), - map((blocks) => blocks.blocks.get(at!)?.hash ?? blocks.finalized), - ) - - const validate$: Observable = at$.pipe( - mergeMap((at) => - chainHead.validateTx$(at, tx).pipe( - filter((x) => !x), - map(() => { - throw new Error("Invalid") - }), - ), - ), - ) - - const track$ = new Observable((observer) => { - const subscription = chainHead.trackTx$(tx).subscribe(observer) - subscription.add( - broadcastTx$(tx).subscribe({ - error(e) { - observer.error(e) - }, - }), - ) - return subscription - }) - - const bestBlockState$ = computeState(track$, chainHead.pinnedBlocks$).pipe( - map((x) => { - if (x === true || x === false) - return getTxEvent("txBestBlocksState", { - found: false, - isValid: x, - }) - - return getTxEvent("txBestBlocksState", { - found: true, - block: { - index: x.index, - hash: x.hash, - }, - ...getTxSuccessFromSystemEvents(x.events, x.index), - }) - }), - ) - - return concat( - emitSign ? of(getTxEvent("signed", {})) : EMPTY, - validate$, - of(getTxEvent("broadcasted", {})), - bestBlockState$.pipe( - continueWith(({ found, type, ...rest }) => - found ? of(getTxEvent("finalized", rest as any)) : EMPTY, - ), - ), - ) -} - -export const submit = async ( - chainHead: ChainHead$, - broadcastTx$: (tx: string) => Observable, - transaction: HexString, - at?: HexString, -): Promise => - lastValueFrom(submit$(chainHead, broadcastTx$, transaction, at)).then((x) => { - if (x.type !== "finalized") throw null - const result: TxFinalizedPayload = { ...x } - delete (result as any).type - return result - }) diff --git a/projects/wallet-template/src/background/tx-helper/tx.ts b/projects/wallet-template/src/background/tx-helper/tx.ts deleted file mode 100644 index 219baa513..000000000 --- a/projects/wallet-template/src/background/tx-helper/tx.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { - AccountId, - Binary, - Enum, - SS58String, - Tuple, - compact, - u128, - u32, - u8, -} from "@polkadot-api/substrate-bindings" -import { fromHex, mergeUint8, toHex } from "@polkadot-api/utils" -import { - Observable, - firstValueFrom, - map, - mergeMap, - take, - throwError, -} from "rxjs" -import { - BlockInfo, - RuntimeContext, - getObservableClient, -} from "@polkadot-api/observable-client" -import type { CompatibilityHelper, Runtime } from "./runtime" -import { PolkadotSigner } from "@polkadot-api/polkadot-signer" -import { getPolkadotSigner } from "@polkadot-api/signer" -import type { AssetDescriptor } from "./descriptors" -import { createTx } from "./create-tx" -import { - TxCall, - TxEntry, - TxPromise, - TxObservable, - TxOptions, - TxSignFn, -} from "./types" -import { submit, submit$ } from "./submit-fns" - -export { submit, submit$ } - -const accountIdEnc = AccountId().enc -const queryInfoRawDec = Tuple(compact, compact, u8, u128).dec -const queryInfoDec = (input: string): bigint => queryInfoRawDec(input)[3] -const fakeSignature = new Uint8Array(64) -const getFakeSignature = () => fakeSignature - -export const createTxEntry = < - Arg extends {} | undefined, - Pallet extends string, - Name extends string, - Asset extends AssetDescriptor, ->( - pallet: Pallet, - name: Name, - assetChecksum: Asset, - chainHead: ReturnType["chainHead$"]>, - broadcast: (tx: string) => Observable, - compatibilityHelper: CompatibilityHelper, -): TxEntry => { - const { isCompatible, compatibleRuntime$ } = compatibilityHelper((ctx) => - ctx.checksumBuilder.buildCall(pallet, name), - ) - const checksumError = () => - new Error(`Incompatible runtime entry Tx(${pallet}.${name})`) - - const fn = (arg?: Arg): any => { - const getCallDataWithContext = ( - { dynamicBuilder, asset: [assetEnc, assetCheck] }: RuntimeContext, - arg: any, - txOptions: Partial<{ asset: any }> = {}, - ) => { - let returnOptions = txOptions - if (txOptions.asset) { - if (assetChecksum !== assetCheck) - throw new Error(`Incompatible runtime asset`) - returnOptions = { ...txOptions, asset: assetEnc(txOptions.asset) } - } - - const { location, codec } = dynamicBuilder.buildCall(pallet, name) - return { - callData: Binary.fromBytes( - mergeUint8(new Uint8Array(location), codec.enc(arg)), - ), - options: returnOptions, - } - } - - const getCallData$ = (arg: any, options: Partial<{ asset: any }> = {}) => - compatibleRuntime$(chainHead, null, checksumError).pipe( - map((ctx) => getCallDataWithContext(ctx, arg, options)), - ) - - const getEncodedData: TxCall = (runtime?: Runtime): any => { - if (!runtime) - return firstValueFrom(getCallData$(arg).pipe(map((x) => x.callData))) - - if (!isCompatible(runtime)) throw checksumError() - return getCallDataWithContext(runtime._getCtx(), arg).callData - } - - const sign$ = ( - from: PolkadotSigner, - { ..._options }: Omit, "at">, - atBlock: BlockInfo, - ) => - getCallData$(arg, _options).pipe( - mergeMap(({ callData, options }) => - createTx(chainHead, from, callData.asBytes(), atBlock, options), - ), - ) - - const _sign = ( - from: PolkadotSigner, - { at, ..._options }: TxOptions<{}> = {}, - ) => { - return ( - !at || at === "finalized" - ? chainHead.finalized$ - : at === "best" - ? chainHead.best$ - : chainHead.bestBlocks$.pipe( - map((x) => x.find((b) => b.hash === at)), - ) - ).pipe( - take(1), - mergeMap((atBlock) => - atBlock - ? sign$(from, _options, atBlock).pipe( - map((signed) => ({ - tx: toHex(signed), - block: atBlock, - })), - ) - : throwError(() => new Error(`Uknown block ${at}`)), - ), - ) - } - - const sign: TxSignFn = (from, options) => - firstValueFrom(_sign(from, options)).then((x) => x.tx) - - const signAndSubmit: TxPromise = (from, _options) => - firstValueFrom(_sign(from, _options)).then(({ tx, block }) => - submit(chainHead, broadcast, tx, block.hash), - ) - - const signSubmitAndWatch: TxObservable = (from, _options) => - _sign(from, _options).pipe( - mergeMap(({ tx, block }) => - submit$(chainHead, broadcast, tx, block.hash, true), - ), - ) - - const getEstimatedFees = async ( - from: Uint8Array | SS58String, - _options?: any, - ) => { - const fakeSigner = getPolkadotSigner( - from instanceof Uint8Array ? from : accountIdEnc(from), - "Sr25519", - getFakeSignature, - ) - const encoded = fromHex(await sign(fakeSigner, _options)) - const args = toHex(mergeUint8(encoded, u32.enc(encoded.length))) - - return firstValueFrom( - chainHead - .call$(null, "TransactionPaymentApi_query_info", args) - .pipe(map(queryInfoDec)), - ) - } - - return { - getEstimatedFees, - decodedCall: { - type: pallet, - value: Enum(name, arg as any), - }, - getEncodedData, - sign, - signSubmitAndWatch, - signAndSubmit, - } - } - - return Object.assign(fn, { isCompatible }) -} diff --git a/projects/wallet-template/src/background/tx-helper/types.ts b/projects/wallet-template/src/background/tx-helper/types.ts deleted file mode 100644 index 232889a67..000000000 --- a/projects/wallet-template/src/background/tx-helper/types.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { IsCompatible, Runtime } from "./runtime" -import { SystemEvent } from "@polkadot-api/observable-client" -import { PolkadotSigner } from "@polkadot-api/polkadot-signer" -import { - Binary, - Enum, - HexString, - SS58String, -} from "@polkadot-api/substrate-bindings" -import { Observable } from "rxjs" - -export type TxEvent = TxSigned | TxBroadcasted | TxBestBlocksState | TxFinalized -export type TxBroadcastEvent = - | TxSigned - | TxBroadcasted - | TxBestBlocksState - | TxFinalized - -export type TxSigned = { type: "signed"; txHash: HexString } - -export type TxBroadcasted = { type: "broadcasted"; txHash: HexString } - -export type TxBestBlocksState = { - type: "txBestBlocksState" - txHash: HexString -} & (TxInBestBlocksNotFound | TxInBestBlocksFound) - -export type TxInBestBlocksNotFound = { - found: false - isValid: boolean -} - -export type TxInBestBlocksFound = { - found: true -} & TxEventsPayload - -export type TxEventsPayload = { - ok: boolean - events: Array - block: { hash: string; index: number } -} - -export type TxFinalized = { - type: "finalized" - txHash: HexString -} & TxEventsPayload - -export type TxOptions = Partial< - void extends Asset - ? { - at: HexString | "best" | "finalized" - tip: bigint - mortality: { mortal: false } | { mortal: true; period: number } - nonce: number - } - : { - at: HexString | "best" | "finalized" - tip: bigint - mortality: { mortal: false } | { mortal: true; period: number } - asset: Asset - nonce: number - } -> - -export type TxFinalizedPayload = Omit -export type TxPromise = ( - from: PolkadotSigner, - txOptions?: TxOptions, -) => Promise - -export type TxObservable = ( - from: PolkadotSigner, - txOptions?: TxOptions, -) => Observable - -export interface TxCall { - (): Promise - (runtime: Runtime): Binary -} - -export type TxSignFn = ( - from: PolkadotSigner, - txOptions?: TxOptions, -) => Promise - -export type Transaction< - Arg extends {} | undefined, - Pallet extends string, - Name extends string, - Asset, -> = { - sign: TxSignFn - signSubmitAndWatch: TxObservable - signAndSubmit: TxPromise - getEncodedData: TxCall - getEstimatedFees: ( - from: Uint8Array | SS58String, - txOptions?: TxOptions, - ) => Promise - decodedCall: Enum<{ [P in Pallet]: Enum<{ [N in Name]: Arg }> }> -} - -export interface TxEntry< - Arg extends {} | undefined, - Pallet extends string, - Name extends string, - Asset, -> { - ( - ...args: Arg extends undefined ? [] : [data: Arg] - ): Transaction - isCompatible: IsCompatible -} diff --git a/projects/wallet-template/src/background/tx-helper/utils/continue-with.ts b/projects/wallet-template/src/background/tx-helper/utils/continue-with.ts deleted file mode 100644 index d8ef2cd02..000000000 --- a/projects/wallet-template/src/background/tx-helper/utils/continue-with.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Observable } from "rxjs" - -let NOTIN = {} -export const continueWith = - ( - mapper: (input: I) => Observable, - ): ((source: Observable) => Observable) => - (source) => - new Observable((observer) => { - let latestValue: I = NOTIN as I - let subscription = source.subscribe({ - next(v) { - observer.next((latestValue = v)) - }, - error(e) { - observer.error(e) - }, - complete() { - if (latestValue === NOTIN) observer.complete() - else subscription = mapper(latestValue).subscribe(observer) - }, - }) - - return () => { - subscription.unsubscribe() - } - }) diff --git a/projects/wallet-template/src/background/tx-helper/utils/index.ts b/projects/wallet-template/src/background/tx-helper/utils/index.ts deleted file mode 100644 index 7c827823c..000000000 --- a/projects/wallet-template/src/background/tx-helper/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./continue-with"