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

feat: identity pattern and example #599

Merged
merged 9 commits into from
Feb 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions codegen/frame/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,15 @@ function createTypeDecl(ctx: FrameCodegen, visitor: TyVisitor<string>, path: str
${docs}
export type ${name} = ${visitor.tupleStruct(ty, members)}
${docs}
export function ${name}(...value: ${path}){ return value }
export function ${name}<X>(...value: C.RunicArgs<X, ${path}>): C.ValueRune<${path}, C.RunicArgs.U<X>> { return C.Rune.tuple(value) }
`
},
objectStruct(ty) {
return `
${docs}
export interface ${name} ${visitor.objectStruct(ty)}
${docs}
export function ${name}(value: ${path}){ return value }
export function ${name}<X>(value: C.RunicArgs<X, ${path}>): C.ValueRune<${path}, C.RunicArgs.U<X>> { return C.Rune.rec(value) }
`
},
option: null!,
Expand Down
23 changes: 23 additions & 0 deletions examples/identity_cr.ts
Original file line number Diff line number Diff line change
@@ -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()
100 changes: 100 additions & 0 deletions patterns/identity.ts
Original file line number Diff line number Diff line change
@@ -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<A extends Record<string, unknown>> {
additional: A
display: string
legal?: string
web?: string
riot?: string
email?: string
pgpFingerprint?: string
image?: string
twitter?: string
}

export class IdentityInfoTranscoders<A extends Record<string, any>> {
constructor(readonly additionalCodecs?: { [K in keyof A]: $.Codec<A[K]> }) {}

encode<X>(props: RunicArgs<X, IdentityInfo<A>>) {
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<Record<typeof REST_KEYS[number], Data>>()
return Rune
.tuple([additional, pgpFingerprint, rest])
.map(([additional, pgpFingerprint, rest]) => ({ additional, pgpFingerprint, ...rest }))
}

decode<X>(...[identityInfo]: RunicArgs<X, [IdentityInfoRaw]>) {
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<T>(codec: $.Codec<T>) {
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"
}
2 changes: 2 additions & 0 deletions words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ cdylib
chainspec
chainspecs
chainx
chelios
chev
childstate
cmds
codegen
Expand Down