-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
19 changed files
with
1,130 additions
and
233 deletions.
There are no files selected for viewing
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import type { Wallet } from "../wallets/wallet.js"; | ||
import { connectWallet } from "./connect-wallet.js"; | ||
import { expect, it, vi } from "vitest"; | ||
|
||
it("should connect a single wallet", async () => { | ||
const wallet = { | ||
connect: vi.fn().mockResolvedValue(undefined), | ||
} as unknown as Wallet; | ||
|
||
await connectWallet(wallet); | ||
|
||
expect(wallet.connect).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it("should connect multiple wallets", async () => { | ||
const wallet1 = { | ||
connect: vi.fn().mockResolvedValue(undefined), | ||
} as unknown as Wallet; | ||
const wallet2 = { | ||
connect: vi.fn().mockResolvedValue(undefined), | ||
} as unknown as Wallet; | ||
|
||
await connectWallet([wallet1, wallet2]); | ||
|
||
expect(wallet1.connect).toHaveBeenCalledTimes(1); | ||
expect(wallet2.connect).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it("should handle an empty array of wallets", async () => { | ||
await expect(connectWallet([])).resolves.toBeUndefined(); | ||
}); | ||
|
||
it("should handle a wallet that fails to connect", async () => { | ||
const wallet = { | ||
connect: vi.fn().mockRejectedValue(new Error("Failed to connect")), | ||
} as unknown as Wallet; | ||
|
||
await expect(connectWallet(wallet)).rejects.toThrow("Failed to connect"); | ||
}); | ||
|
||
it("should handle multiple wallets where one fails to connect", async () => { | ||
const wallet1 = { | ||
connect: vi.fn().mockResolvedValue(undefined), | ||
} as unknown as Wallet; | ||
|
||
const wallet2 = { | ||
connect: vi.fn().mockRejectedValue(new Error("Failed to connect")), | ||
} as unknown as Wallet; | ||
|
||
await expect(connectWallet([wallet1, wallet2])).rejects.toThrow( | ||
"Failed to connect", | ||
); | ||
expect(wallet1.connect).toHaveBeenCalledTimes(1); | ||
expect(wallet2.connect).toHaveBeenCalledTimes(1); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import type { Wallet } from "../wallets/wallet.js"; | ||
import { disconnectWallet } from "./disconnect-wallet.js"; | ||
import { expect, it, vi } from "vitest"; | ||
|
||
it("should disconnect a single wallet", async () => { | ||
const mockWallet = { | ||
disconnect: vi.fn().mockResolvedValue(undefined), | ||
} as unknown as Wallet; | ||
|
||
await disconnectWallet(mockWallet); | ||
|
||
expect(mockWallet.disconnect).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it("should disconnect multiple wallets", async () => { | ||
const mockWallet1 = { | ||
disconnect: vi.fn().mockResolvedValue(undefined), | ||
} as unknown as Wallet; | ||
|
||
const mockWallet2 = { | ||
disconnect: vi.fn().mockResolvedValue(undefined), | ||
} as unknown as Wallet; | ||
|
||
await disconnectWallet([mockWallet1, mockWallet2]); | ||
|
||
expect(mockWallet1.disconnect).toHaveBeenCalledTimes(1); | ||
expect(mockWallet2.disconnect).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it("should handle an empty array of wallets", async () => { | ||
await expect(disconnectWallet([])).resolves.toBeUndefined(); | ||
}); | ||
|
||
it("should handle a wallet that fails to disconnect", async () => { | ||
const mockWallet = { | ||
disconnect: vi.fn().mockRejectedValue(new Error("Failed to disconnect")), | ||
} as unknown as Wallet; | ||
|
||
await expect(disconnectWallet(mockWallet)).rejects.toThrow( | ||
"Failed to disconnect", | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
/* v8 ignore start */ | ||
import { | ||
getDynamicBuilder, | ||
getLookupFn, | ||
} from "@polkadot-api/metadata-builders"; | ||
import { | ||
Bytes, | ||
enhanceCodec, | ||
metadata as metadataCodec, | ||
Struct, | ||
u8, | ||
type V15, | ||
} from "@polkadot-api/substrate-bindings"; | ||
import type { | ||
Binary, | ||
ChainDefinition, | ||
Codec, | ||
Enum, | ||
FixedSizeBinary, | ||
PolkadotClient, | ||
SS58String, | ||
TypedApi, | ||
} from "polkadot-api"; | ||
|
||
type MultiAddress = Enum<{ | ||
Id: SS58String; | ||
Index: number | bigint; | ||
Raw: Binary; | ||
Address32: FixedSizeBinary<32>; | ||
Address20: FixedSizeBinary<20>; | ||
}>; | ||
|
||
type MultiSignature = Enum<{ | ||
Ed25519: FixedSizeBinary<64>; | ||
Sr25519: FixedSizeBinary<64>; | ||
Ecdsa: FixedSizeBinary<65>; | ||
}>; | ||
|
||
type Extra = Partial<{ | ||
nonZeroSender: undefined; | ||
specVersion: undefined; | ||
txVersion: undefined; | ||
genesis: undefined; | ||
mortality: { type: `Mortal${string}`; value: number }; | ||
nonce: number; | ||
weight: undefined; | ||
transactionPayment: bigint; | ||
metadataHash: Enum<{ Disabled: undefined; Enabled: undefined }>; | ||
[key: string]: unknown; | ||
}>; | ||
|
||
type Call = { | ||
module: string; | ||
func: string; | ||
args: unknown; | ||
}; | ||
|
||
type Extrinsic = { version: number; call: Call } & ( | ||
| { signed: false } | ||
| { | ||
signed: true; | ||
sender: MultiAddress; | ||
signature: MultiSignature; | ||
extra: Extra; | ||
} | ||
); | ||
|
||
export async function unstable_getBlockExtrinsics( | ||
client: PolkadotClient, | ||
typedApi: TypedApi<ChainDefinition>, | ||
blockHash: string, | ||
) { | ||
const v15MetadataBinary = await ( | ||
typedApi.apis["Metadata"]!["metadata_at_version"]! as ( | ||
version: number, | ||
) => Promise<Binary | undefined> | ||
)(15); | ||
|
||
if (v15MetadataBinary === undefined) { | ||
return; | ||
} | ||
|
||
const metadataResult = metadataCodec.dec(v15MetadataBinary.asBytes()); | ||
|
||
if (metadataResult.metadata.tag !== "v15") { | ||
return; | ||
} | ||
|
||
const metadata = metadataResult.metadata.value; | ||
|
||
const dynamicBuilder = await getOrCreateDynamicBuilder(client, metadata); | ||
|
||
const version$ = enhanceCodec( | ||
u8, | ||
(value: { signed: boolean; version: number }) => | ||
(+!!value.signed << 7) | value.version, | ||
(value) => ({ | ||
version: value & ~(1 << 7), | ||
signed: !!(value & (1 << 7)), | ||
}), | ||
); | ||
|
||
const address$ = dynamicBuilder.buildDefinition( | ||
metadata.extrinsic.address, | ||
) as Codec<MultiAddress>; | ||
|
||
const signature$ = dynamicBuilder.buildDefinition( | ||
metadata.extrinsic.signature, | ||
) as Codec<MultiSignature>; | ||
|
||
const rawExtra$ = dynamicBuilder.buildDefinition( | ||
metadata.extrinsic.extra, | ||
) as Codec<unknown[]>; | ||
|
||
const extra$ = enhanceCodec( | ||
rawExtra$, | ||
(extra: Extra) => | ||
metadata.extrinsic.signedExtensions.map( | ||
(signedExtension) => | ||
extra[ | ||
"Check" + | ||
signedExtension.identifier.slice(0, 1).toUpperCase() + | ||
signedExtension.identifier.slice(1, 0) | ||
], | ||
), | ||
(extra) => | ||
Object.fromEntries( | ||
metadata.extrinsic.signedExtensions.map((signedExtension, index) => { | ||
const name = signedExtension.identifier.replace(/^Check/, ""); | ||
return [ | ||
name.slice(0, 1).toLowerCase() + name.slice(1), | ||
extra[index], | ||
] as const; | ||
}), | ||
) as Extra, | ||
); | ||
|
||
const rawCall$ = dynamicBuilder.buildDefinition( | ||
metadata.extrinsic.call, | ||
) as Codec<{ type: string; value: { type: string; value: unknown } }>; | ||
|
||
const call$ = enhanceCodec( | ||
rawCall$, | ||
(call: Call) => ({ | ||
type: call.module, | ||
value: { type: call.func, value: call.args }, | ||
}), | ||
(call) => ({ | ||
module: call.type, | ||
func: call.value.type, | ||
args: call.value.value, | ||
}), | ||
); | ||
|
||
const inherentExtrinsic$ = Struct({ | ||
version: version$ as Codec<{ version: number; signed: false }>, | ||
body: Struct({ call: call$ }), | ||
}); | ||
|
||
const signedExtrinsic$ = Struct({ | ||
version: version$ as Codec<{ version: number; signed: true }>, | ||
body: Struct({ | ||
sender: address$, | ||
signature: signature$, | ||
extra: extra$, | ||
call: call$, | ||
}), | ||
}); | ||
|
||
const simpleVersion$ = Struct({ | ||
version: version$, | ||
}); | ||
|
||
const extrinsic$ = enhanceCodec( | ||
Bytes(), | ||
(extrinsic: Extrinsic) => | ||
extrinsic.signed | ||
? signedExtrinsic$.enc({ | ||
version: { version: extrinsic.version, signed: extrinsic.signed }, | ||
body: { | ||
sender: extrinsic.sender, | ||
signature: extrinsic.signature, | ||
extra: extrinsic.extra, | ||
call: extrinsic.call, | ||
}, | ||
}) | ||
: inherentExtrinsic$.enc({ | ||
version: { version: extrinsic.version, signed: extrinsic.signed }, | ||
body: { call: extrinsic.call }, | ||
}), | ||
(extrinsicBytes) => { | ||
const { | ||
version: { signed }, | ||
} = simpleVersion$.dec(extrinsicBytes); | ||
|
||
const rawExtrinsic = ( | ||
signed ? signedExtrinsic$.dec : inherentExtrinsic$.dec | ||
)(extrinsicBytes); | ||
|
||
return { ...rawExtrinsic.version, ...rawExtrinsic.body } as Extrinsic; | ||
}, | ||
); | ||
|
||
const blockBody = await client.getBlockBody(blockHash); | ||
|
||
return blockBody.map(extrinsic$.dec); | ||
} | ||
|
||
const dynamicBuilders = new WeakMap< | ||
PolkadotClient, | ||
ReturnType<typeof getDynamicBuilder> | ||
>(); | ||
|
||
async function getOrCreateDynamicBuilder( | ||
client: PolkadotClient, | ||
metadata: V15, | ||
) { | ||
if (dynamicBuilders.has(client)) { | ||
return dynamicBuilders.get(client)!; | ||
} | ||
|
||
const lookup = getLookupFn(metadata); | ||
const dynamicBuilder = getDynamicBuilder(lookup); | ||
|
||
return dynamicBuilders.set(client, dynamicBuilder).get(client)!; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { getBlock, type GetBlockOptions } from "./get-block.js"; | ||
import type { PolkadotClient } from "polkadot-api"; | ||
import { of } from "rxjs"; | ||
import { expect, it } from "vitest"; | ||
|
||
type DummyBlock = { id: string }; | ||
|
||
it("should return the best block when options.tag is 'best'", () => | ||
new Promise<void>((resolve) => { | ||
const fakeBlock: DummyBlock = { id: "best-block" }; | ||
const client = { | ||
bestBlocks$: of([fakeBlock]), | ||
finalizedBlock$: of({ id: "finalized-block" }), | ||
} as unknown as PolkadotClient; | ||
|
||
getBlock(client, { tag: "best" } as GetBlockOptions).subscribe((result) => { | ||
expect(result).toBe(fakeBlock); | ||
resolve(); | ||
}); | ||
})); | ||
|
||
it("should return the finalized block when options.tag is 'finalized'", () => | ||
new Promise<void>((resolve) => { | ||
const fakeBlock: DummyBlock = { id: "finalized-block" }; | ||
const client = { | ||
bestBlocks$: of([{ id: "best-block" }]), | ||
finalizedBlock$: of(fakeBlock), | ||
} as unknown as PolkadotClient; | ||
|
||
getBlock(client, { tag: "finalized" } as GetBlockOptions).subscribe( | ||
(result) => { | ||
expect(result).toBe(fakeBlock); | ||
resolve(); | ||
}, | ||
); | ||
})); | ||
|
||
it("should return the finalized block when no options are provided", () => | ||
new Promise<void>((resolve) => { | ||
const fakeBlock: DummyBlock = { id: "finalized-block" }; | ||
const client = { | ||
bestBlocks$: of([{ id: "best-block" }]), | ||
finalizedBlock$: of(fakeBlock), | ||
} as unknown as PolkadotClient; | ||
|
||
getBlock(client).subscribe((result) => { | ||
expect(result).toBe(fakeBlock); | ||
resolve(); | ||
}); | ||
})); |
Oops, something went wrong.