Skip to content
This repository was archived by the owner on Sep 14, 2023. It is now read-only.

Commit 406ca84

Browse files
authored
feat: polkadot signer (#244)
1 parent 8be489f commit 406ca84

File tree

6 files changed

+155
-24
lines changed

6 files changed

+155
-24
lines changed

effect/atoms/$Extrinsic.ts

+2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ export const $extrinsic = atomFactory("ExtrinsicCodec", (
55
deriveCodec: M.DeriveCodec,
66
metadata: M.Metadata,
77
sign?: M.SignExtrinsic,
8+
prefix?: number,
89
) => {
910
return M.$extrinsic({
1011
deriveCodec,
1112
metadata,
1213
sign: sign!,
14+
prefix: prefix!,
1315
});
1416
});

effect/std/submitAndWatchExtrinsic.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export interface SendAndWatchExtrinsicProps {
3838
export function sendAndWatchExtrinsic<Props extends SendAndWatchExtrinsicProps>(props: Props) {
3939
const metadata = a.metadata(props.config);
4040
const deriveCodec = a.deriveCodec(metadata);
41-
const $extrinsic = a.$extrinsic(deriveCodec, metadata, props.sign);
41+
const $extrinsic = a.$extrinsic(deriveCodec, metadata, props.sign, props.config.addressPrefix);
4242
const runtimeVersion = a.rpcCall(props.config, "state_getRuntimeVersion", []);
4343
const senderSs58 = sys.anon([props.sender], (sender) => {
4444
return ((): string => {

examples/polkadot_signer.ts

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { TypeRegistry } from "https://deno.land/x/[email protected]/types/mod.ts";
2+
import * as C from "../mod.ts";
3+
import * as t from "../test-util/mod.ts";
4+
import * as U from "../util/mod.ts";
5+
6+
const config = await t.config();
7+
8+
const root = C
9+
.chain(config)
10+
.pallet("Balances")
11+
.extrinsic("transfer")
12+
.call({
13+
value: 12345n,
14+
dest: {
15+
type: "Id",
16+
value: t.bob.publicKey,
17+
},
18+
})
19+
.signed(
20+
{
21+
type: "Id",
22+
value: t.alice.publicKey,
23+
},
24+
{
25+
signPayload(payload: any) {
26+
const tr = new TypeRegistry();
27+
tr.setSignedExtensions(payload.signedExtensions);
28+
return Promise.resolve(
29+
tr
30+
.createType("ExtrinsicPayload", payload, { version: payload.version })
31+
.sign(t.alice),
32+
);
33+
},
34+
},
35+
)
36+
.sendAndWatch((stop) => {
37+
return (event) => {
38+
if (typeof event.params.result === "string") {
39+
console.log("Extrinsic", event.params.result);
40+
} else {
41+
if (event.params.result.inBlock) {
42+
console.log("Extrinsic in block", event.params.result.inBlock);
43+
} else if (event.params.result.finalized) {
44+
console.log("Extrinsic finalized as of", event.params.result.finalized);
45+
stop();
46+
} else {
47+
console.log("Misc", event.params.result);
48+
}
49+
}
50+
};
51+
});
52+
53+
U.throwIfError(await root.run());

frame_metadata/Extrinsic.ts

+94-22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import * as $ from "../deps/scale.ts";
22
import { assert } from "../deps/std/testing/asserts.ts";
33
import * as H from "../hashers/mod.ts";
4+
import * as ss58 from "../ss58/mod.ts";
5+
import { hex } from "../util/mod.ts";
46
import { $null, DeriveCodec } from "./Codec.ts";
57
import { Metadata } from "./Metadata.ts";
68

@@ -21,7 +23,13 @@ export interface Signature {
2123
value: Uint8Array;
2224
}
2325

24-
export type SignExtrinsic = (message: Uint8Array) => Signature | Promise<Signature>;
26+
export type SignExtrinsic =
27+
| ((message: Uint8Array) => Signature | Promise<Signature>)
28+
| PolkadotSigner;
29+
30+
export interface PolkadotSigner {
31+
signPayload(payload: any): Promise<{ signature: string }>;
32+
}
2533

2634
export interface Extrinsic {
2735
protocolVersion: number;
@@ -41,6 +49,7 @@ interface ExtrinsicCodecProps {
4149
metadata: Metadata;
4250
deriveCodec: DeriveCodec;
4351
sign: SignExtrinsic;
52+
prefix: number;
4453
}
4554

4655
export function $extrinsic(props: ExtrinsicCodecProps): $.Codec<Extrinsic> {
@@ -53,8 +62,12 @@ export function $extrinsic(props: ExtrinsicCodecProps): $.Codec<Extrinsic> {
5362
const callTy = props.metadata.tys[callTyI];
5463
assert(callTy?.type === "Union");
5564
const $call = deriveCodec(callTyI);
56-
const $extra = getExtrasCodec(signedExtensions.map((x) => x.ty));
57-
const $additional = getExtrasCodec(signedExtensions.map((x) => x.additionalSigned));
65+
const [$extra, extraPjsInfo] = getExtensionInfo(pjsExtraKeyMap, "ty");
66+
const [$additional, additionalPjsInfo] = getExtensionInfo(
67+
pjsAdditionalKeyMap,
68+
"additionalSigned",
69+
);
70+
const pjsInfo = [...extraPjsInfo, ...additionalPjsInfo];
5871

5972
const toSignSize = $call._staticSize + $extra._staticSize + $additional._staticSize;
6073
const totalSize = 1 + $address._staticSize + $sig._staticSize + toSignSize;
@@ -74,30 +87,67 @@ export function $extrinsic(props: ExtrinsicCodecProps): $.Codec<Extrinsic> {
7487
};
7588
const { signature } = extrinsic;
7689
if (signature) {
90+
$address._encode(buffer, signature.address);
7791
if ("additional" in signature) {
78-
$address._encode(buffer, signature.address);
7992
const toSignBuffer = new $.EncodeBuffer(buffer.stealAlloc(toSignSize));
8093
$call._encode(toSignBuffer, call);
8194
const callEnd = toSignBuffer.finishedSize + toSignBuffer.index;
82-
$extra._encode(toSignBuffer, signature.extra);
83-
const extraEnd = toSignBuffer.finishedSize + toSignBuffer.index;
84-
$additional._encode(toSignBuffer, signature.additional);
85-
const toSignEncoded = toSignBuffer.finish();
86-
const callEncoded = toSignEncoded.subarray(0, callEnd);
87-
const extraEncoded = toSignEncoded.subarray(callEnd, extraEnd);
88-
const toSign = toSignEncoded.length > 256
89-
? H.Blake2_256.hash(toSignEncoded)
90-
: toSignEncoded;
91-
const sig = props.sign(toSign);
92-
if (sig instanceof Promise) {
93-
$sigPromise._encode(buffer, sig);
95+
if ("signPayload" in props.sign) {
96+
const exts = [...signature.extra, ...signature.additional];
97+
const extEnds = [];
98+
for (let i = 0; i < pjsInfo.length; i++) {
99+
pjsInfo[i]!.codec._encode(toSignBuffer, exts[i]);
100+
extEnds.push(toSignBuffer.finishedSize + toSignBuffer.index);
101+
}
102+
const extraEnd = extEnds[extraPjsInfo.length - 1] ?? callEnd;
103+
const toSignEncoded = toSignBuffer.finish();
104+
const callEncoded = toSignEncoded.subarray(0, callEnd);
105+
const extraEncoded = toSignEncoded.subarray(callEnd, extraEnd);
106+
if (signature.address.type !== "Id") {
107+
throw new Error("polkadot signer: address types other than Id are not supported");
108+
}
109+
const payload: Record<string, unknown> = {
110+
address: ss58.encode(props.prefix, signature.address.value),
111+
method: hex.encodePrefixed(callEncoded),
112+
signedExtensions: signedExtensions.map((x) => x.ident),
113+
version: extrinsic.protocolVersion,
114+
};
115+
let last = callEnd;
116+
for (let i = 0; i < pjsInfo.length; i++) {
117+
const { key } = pjsInfo[i]!;
118+
if (!key) throw new Error("polkadot signer: unknown extension");
119+
payload[key] = typeof exts[i] === "number"
120+
? exts[i]
121+
: hex.encodePrefixed(toSignEncoded.subarray(last, extEnds[i]!));
122+
last = extEnds[i]!;
123+
}
124+
const signer = props.sign;
125+
buffer.writeAsync(0, async (buffer) => {
126+
const { signature } = await signer.signPayload(payload);
127+
buffer.insertArray(hex.decode(signature));
128+
});
129+
buffer.insertArray(extraEncoded);
130+
buffer.insertArray(callEncoded);
94131
} else {
95-
$sig._encode(buffer, sig);
132+
$extra._encode(toSignBuffer, signature.extra);
133+
const extraEnd = toSignBuffer.finishedSize + toSignBuffer.index;
134+
$additional._encode(toSignBuffer, signature.additional);
135+
const toSignEncoded = toSignBuffer.finish();
136+
const callEncoded = toSignEncoded.subarray(0, callEnd);
137+
const extraEncoded = toSignEncoded.subarray(callEnd, extraEnd);
138+
const toSign = toSignEncoded.length > 256
139+
? H.Blake2_256.hash(toSignEncoded)
140+
: toSignEncoded;
141+
const sig = props.sign(toSign);
142+
if (sig instanceof Promise) {
143+
$sigPromise._encode(buffer, sig);
144+
} else {
145+
$sig._encode(buffer, sig);
146+
}
147+
buffer.insertArray(extraEncoded);
148+
buffer.insertArray(callEncoded);
96149
}
97-
buffer.insertArray(extraEncoded);
98-
buffer.insertArray(callEncoded);
99150
} else {
100-
$address._encode(buffer, signature.address);
101151
$sig._encode(buffer, signature.sig);
102152
$extra._encode(buffer, signature.extra);
103153
$call._encode(buffer, call);
@@ -146,7 +196,29 @@ export function $extrinsic(props: ExtrinsicCodecProps): $.Codec<Extrinsic> {
146196
function findExtrinsicTypeParam(name: string) {
147197
return metadata.tys[metadata.extrinsic.ty]?.params.find((x) => x.name === name)?.ty;
148198
}
149-
function getExtrasCodec(is: number[]) {
150-
return $.tuple(...is.map((i) => deriveCodec(i)).filter((x) => x !== $null));
199+
function getExtensionInfo(
200+
keyMap: Record<string, string | undefined>,
201+
key: "ty" | "additionalSigned",
202+
): [codec: $.Codec<any>, pjsInfo: { key: string | undefined; codec: $.Codec<any> }[]] {
203+
const pjsInfo = signedExtensions
204+
.map((e) => ({ key: keyMap[e.ident], codec: deriveCodec(e[key]) }))
205+
.filter((x) => x.codec !== $null);
206+
return [$.tuple(...pjsInfo.map((x) => x.codec)), pjsInfo];
151207
}
152208
}
209+
210+
const pjsExtraKeyMap: Record<string, string> = {
211+
CheckEra: "era",
212+
CheckMortality: "era",
213+
ChargeTransactionPayment: "tip",
214+
CheckNonce: "nonce",
215+
};
216+
217+
const pjsAdditionalKeyMap: Record<string, string> = {
218+
CheckEra: "blockHash",
219+
CheckMortality: "blockHash",
220+
CheckSpecVersion: "specVersion",
221+
CheckTxVersion: "transactionVersion",
222+
CheckVersion: "specVersion",
223+
CheckGenesis: "genesisHash",
224+
};

known/configs.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,5 @@ export const statemint = new Statemint();
3434
export class Subsocial extends Config_("wss://para.subsocial.network", 28) {}
3535
export const subsocial = new Subsocial();
3636

37-
export class Westend extends Config_("wss://westend-rpc.polkadot.io", 0) {}
37+
export class Westend extends Config_("wss://westend-rpc.polkadot.io", 42) {}
3838
export const westend = new Westend();

util/hex.ts

+4
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,7 @@ export function encode(bytes: Uint8Array): string {
1717
}
1818
return str;
1919
}
20+
21+
export function encodePrefixed(bytes: Uint8Array): string {
22+
return "0x" + encode(bytes);
23+
}

0 commit comments

Comments
 (0)