diff --git a/codegen/frame/type.ts b/codegen/frame/type.ts index e82b9bdca..d8a39395b 100644 --- a/codegen/frame/type.ts +++ b/codegen/frame/type.ts @@ -74,7 +74,7 @@ function createTypeDecl(ctx: FrameCodegen, visitor: TyVisitor, path: str ${docs} export type ${name} = ${visitor.tupleStruct(ty, members)} ${docs} - export function ${name}(...value: ${path}){ return value } + export function ${name}(...value: C.RunicArgs): C.ValueRune<${path}, C.RunicArgs.U> { return C.Rune.tuple(value) } ` }, objectStruct(ty) { @@ -82,7 +82,7 @@ function createTypeDecl(ctx: FrameCodegen, visitor: TyVisitor, path: str ${docs} export interface ${name} ${visitor.objectStruct(ty)} ${docs} - export function ${name}(value: ${path}){ return value } + export function ${name}(value: C.RunicArgs): C.ValueRune<${path}, C.RunicArgs.U> { return C.Rune.rec(value) } ` }, option: null!, diff --git a/examples/identity_cr.ts b/examples/identity_cr.ts new file mode 100644 index 000000000..aa4364f97 --- /dev/null +++ b/examples/identity_cr.ts @@ -0,0 +1,23 @@ +import { $, alice } from "capi" +import { IdentityInfoTranscoders } from "capi/patterns/identity.ts" +import { Identity } from "polkadot_dev/mod.ts" + +const transcoders = new IdentityInfoTranscoders({ stars: $.u8 }) + +await Identity + .setIdentity({ + info: transcoders.encode({ + display: "Chev Chelios", + additional: { stars: 5 }, + }), + }) + .signed({ sender: alice }) + .sent() + .logStatus() + .finalized() + .run() + +await transcoders + .decode(Identity.IdentityOf.entry([alice.publicKey]).access("info")) + .log() + .run() diff --git a/patterns/identity.ts b/patterns/identity.ts new file mode 100644 index 000000000..5eeef2ceb --- /dev/null +++ b/patterns/identity.ts @@ -0,0 +1,100 @@ +import { Data, IdentityInfo as IdentityInfoRaw } from "polkadot_dev/types/pallet_identity/types.ts" +import * as $ from "../deps/scale.ts" +import { Rune, RunicArgs } from "../rune/mod.ts" + +export interface IdentityInfo> { + additional: A + display: string + legal?: string + web?: string + riot?: string + email?: string + pgpFingerprint?: string + image?: string + twitter?: string +} + +export class IdentityInfoTranscoders> { + constructor(readonly additionalCodecs?: { [K in keyof A]: $.Codec }) {} + + encode(props: RunicArgs>) { + const { additionalCodecs } = this + const additional = additionalCodecs + ? Rune + .resolve(props.additional) + .map((additional) => + Object + .entries(additionalCodecs) + .map(([k, $v]) => [encodeStr(k), encoder($v)(additional[k])] as [Data, Data]) + ) + .throws(IdentityDataTooLargeError) + : [] + const pgpFingerprint = Rune + .resolve(props.pgpFingerprint) + .unhandle(undefined).map((v) => $.str.encode(v)) + .rehandle(undefined) + const rest = Rune + .rec(Object.fromEntries(REST_KEYS.map((key) => [ + key, + Rune + .resolve(props[key]) + .unhandle(undefined) + .map(encodeStr) + .rehandle(undefined, () => Data.None()), + ]))) + .unsafeAs>() + return Rune + .tuple([additional, pgpFingerprint, rest]) + .map(([additional, pgpFingerprint, rest]) => ({ additional, pgpFingerprint, ...rest })) + } + + decode(...[identityInfo]: RunicArgs) { + const { additionalCodecs } = this + return Rune + .resolve(identityInfo) + .map(({ + additional: additionalRaw, + pgpFingerprint: pgpFingerprintRaw, + ...restRaw + }) => { + const additional = additionalCodecs + ? Object.fromEntries(additionalRaw.map(([kd, vd]) => { + const k: keyof A = "value" in kd ? $.str.decode(kd.value) : (() => { + throw new CouldNotDecodeIdentityInfoAdditionalKey() + })() + return [k, vd.type === "None" ? undefined : additionalCodecs![k]!.decode(vd.value)] + })) + : {} + const pgpFingerprint = pgpFingerprintRaw ? $.str.decode(pgpFingerprintRaw) : undefined + const rest = Object.fromEntries( + Object + .entries(restRaw) + .map(([key, data]) => [ + key, + data.type === "None" ? undefined : $.str.decode(data.value), + ]), + ) + return { pgpFingerprint, additional, ...rest } + }) + .throws(CouldNotDecodeIdentityInfoAdditionalKey) + } +} + +const encodeStr = encoder($.str) +function encoder(codec: $.Codec) { + return (value: T) => { + const encoded = codec.encode(value) + const { length } = encoded + if (length > 32) throw new IdentityDataTooLargeError() + return { type: `Raw${length}`, value: encoded } as Data + } +} + +const REST_KEYS = ["display", "legal", "web", "riot", "email", "image", "twitter"] as const + +export class IdentityDataTooLargeError extends Error { + override readonly name = "IdentityDataTooLargeError" +} +export class CouldNotDecodeIdentityInfoAdditionalKey extends Error { + override readonly name = "CouldNotDecodeIdentityInfoAdditionalKey" +} diff --git a/words.txt b/words.txt index 65ca0a466..d8cd68cfc 100644 --- a/words.txt +++ b/words.txt @@ -26,6 +26,8 @@ cdylib chainspec chainspecs chainx +chelios +chev childstate cmds codegen