paritytech / capi

[WIP] A framework for crafting interactions with Substrate chains
https://docs.capi.dev
Apache License 2.0
105 stars 10 forks source link

Pattern for multisig behind a proxy #1000

Open Tbaut opened 1 year ago

Tbaut commented 1 year ago

Feature Request

As discussed with Harry, here's an issue to talk about the main use case I, and many other users have. we are managing multisigs that are behind a proxy. Yet unlike what I saw with the virtual pattern https://github.com/paritytech/capi/blob/main/examples/multisig/virtual.eg.ts, our multisig is formed by accounts directly, and not pure proxies. The multisig is the controller of a pure proxy though, which gives a lot of flexibility.

Suggestion

Have an example/pattern with this use case:

Several accounts > multisig > pure proxy

This is the default behavior of Multix, a simple interface to manage complex multisigs. There is IMHO no easier option to manage a multisig. Having acounts > proxies > multisig > proxy as the virtual pattern currently has is costly, complex, and doesn't add security.

ryanleecode commented 1 year ago

Doesn't this example satisfy that requirement? The example showcases the multisig creating the stash. We can also add another example where you can supply a stash.

https://github.com/paritytech/capi/blob/main/examples/multisig/stash.eg.ts


import { MultiAddress, polkadotDev } from "@capi/polkadot-dev"
import { createDevUsers, Rune } from "capi"
import { MultisigRune } from "capi/patterns/multisig/mod.ts"
import { filterPureCreatedEvents, replaceDelegateCalls } from "capi/patterns/proxy/mod.ts"
import { signature } from "capi/patterns/signature/polkadot.ts"

const { alexa, billy, carol } = await createDevUsers()

const multisig = MultisigRune.from(polkadotDev, {
  signatories: [alexa, billy, carol].map(({ publicKey }) => publicKey),
  threshold: 2,
})

await multisig
  .fund(20_000_000_000_000n)
  .signed(signature({ sender: alexa }))
  .sent()
  .dbgStatus("Existential deposit:")
  .finalized()
  .run()

const stash = await polkadotDev.Proxy.createPure({
  proxyType: "Any",
  delay: 0,
  index: 0,
}).signed(signature({ sender: alexa }))
  .sent()
  .dbgStatus("Create Stash:")
  .finalizedEvents()
  .unhandleFailed()
  .pipe(filterPureCreatedEvents)
  .map((events) => events.map(({ pure }) => pure))
  .access(0)
  .run()

await polkadotDev.Balances
  .transfer({
    value: 1_000_000_000_000n,
    dest: MultiAddress.Id(stash),
  })
  .signed(signature({ sender: alexa }))
  .sent()
  .dbgStatus("Transfer:")
  .finalized()
  .run()

await polkadotDev.Utility.batchAll({
  calls: Rune.array(replaceDelegateCalls(
    polkadotDev,
    MultiAddress.Id(stash),
    alexa.address,
    multisig.address,
  )),
}).signed(signature({ sender: alexa }))
  .sent()
  .dbgStatus("Ownership swaps:")
  .finalized()
  .run()
Tbaut commented 1 year ago

Thanks Ryan, tbh I either didn't see, or had forgotten this example. This is going almost as far as needed. There's a multisig that creates a pure proxy. But there is no transaction made by this pure proxy. Ideally we would have a transaction (say a balances.transfer) initiated by a signatory of the multisig (alexa, billy or carol) and sent as a proxy.proxy for the stash to execute.