import "../../_dnt.polyfills.js"; import { MultiAddress } from "@capi/polkadot" import { equals } from "../../deps/std/bytes.js" import { $, Chain, ChainRune, CodecRune, hex, is, PatternRune, Rune, RunicArgs, ss58, ValueRune, } from "../../mod.js" import { compareBytes } from "../../util/mod.js" import { multisigAccountId } from "./multisigAccountId.js" export interface Multisig { signatories: Uint8Array[] threshold?: number } export const $multisig: $.Codec = $.object( $.field("signatories", $.array($.sizedUint8Array(32))), $.optionalField("threshold", $.u8), ) // TODO: swap out `Chain` constraints upon subset gen issue resolution... same for other patterns export class MultisigRune extends PatternRune { static from( chain: ChainRune, ...[multisig]: RunicArgs ) { return Rune.resolve(multisig).into(MultisigRune, chain) } private storage = this.chain.pallet("Multisig").storage("Multisigs") private value = this.into(ValueRune) threshold = this.value.map(({ threshold, signatories }) => threshold ?? signatories.length - 1) accountId = Rune.fn(multisigAccountId).call(this.value.access("signatories"), this.threshold) address = MultiAddress.Id(this.accountId) ss58 = Rune .tuple([this.chain.addressPrefix(), this.accountId]) .map(([prefix, accountId]: any) => ss58.encode(prefix, accountId)) encoded = CodecRune.from($multisig).encoded(this.as(MultisigRune)) hex = this.encoded.map(hex.encode) otherSignatories(...[sender]: RunicArgs) { return Rune .tuple([this.into(ValueRune).access("signatories"), sender]) .map(([signatories, sender]) => signatories .filter((value) => !equals(value, sender.value!)) .sort(compareBytes) ) } ratify( ...[sender, call_, nonExecuting]: RunicArgs< X, [sender: MultiAddress, call: Chain.Call, nonExecuting?: boolean] > ) { const call = this.chain.extrinsic(Rune.resolve(call_).unsafeAs>()) const otherSignatories = this.otherSignatories(sender) const maybeTimepoint = this.maybeTimepoint(call.callHash) const threshold = this.threshold return this.chain.extrinsic( Rune .object({ type: "Multisig", value: nonExecuting ? Rune.object({ type: "approveAsMulti", threshold, callHash: call.callHash, otherSignatories, maxWeight: { refTime: 0n, proofSize: 0n }, maybeTimepoint, }) : Rune.object({ type: "asMulti", threshold, call, otherSignatories, maxWeight: call.weight(), maybeTimepoint, }), }) .unsafeAs>(), ) } cancel(...[sender, callHash]: RunicArgs) { return this.chain.extrinsic( Rune .object({ type: "Multisig", value: Rune.object({ type: "cancelAsMulti", threshold: this.threshold, callHash, otherSignatories: this.otherSignatories(sender), timepoint: this.maybeTimepoint(callHash).map((x: any) => x ?? new NoProposalError()), }), }) .unsafeAs>(), ) } fund(...[amount]: RunicArgs) { return this.chain.extrinsic( Rune .object({ type: "Balances", value: Rune.object({ type: "transfer", dest: this.address, value: amount, }), }) .unsafeAs>(), ) } maybeTimepoint( ...[callHash, blockHash]: RunicArgs ) { return this .proposal(callHash, blockHash) .unhandle(is(undefined)) .access("when") .rehandle(is(undefined)) } // TODO: why the type errors? proposals( ...[limit, blockHash]: RunicArgs ): ValueRune[], RunicArgs.U | U> { const partialKey = Rune.tuple([this.accountId]) return this.storage.keys({ limit, partialKey: partialKey as never, }, blockHash) as never } proposal(...[callHash, blockHash]: RunicArgs) { return this.storage .value( Rune .tuple([this.accountId, callHash]) .unsafeAs<$.Output["key"]>>(), blockHash, ) .unsafeAs< undefined | { when: { height: number; index: number } approvals: Uint8Array[] depositor: Uint8Array } >() .into(ValueRune) } isProposed( ...[callHash, blockHash]: RunicArgs ) { return this.storage .valueRaw( Rune .tuple([this.accountId, callHash]) .unsafeAs<$.Output["key"]>>(), blockHash, ) .map((entry) => entry !== null) } static fromHex( chain: ChainRune, ...[state]: RunicArgs ) { return CodecRune.from($multisig) .decoded(Rune.resolve(state).into(ValueRune).map(hex.decode)) .into(MultisigRune, chain) } } export class NoProposalError extends Error { override readonly name = "NoProposalError" }