-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #42 from polkadot-api/vo-gov-sdk
feat(governance): Add docs for governance SDK
- Loading branch information
Showing
5 changed files
with
367 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
# Bounties SDK | ||
|
||
The Bounties SDK provides the following features: | ||
|
||
- Loads descriptions of each bounty. | ||
- Matches referenda related to the bounty. | ||
- Looks for scheduled changes in the bounty status. | ||
- Abstracts different states into a TypeScript-friendly interface. | ||
|
||
## Getting Started | ||
|
||
Install the Governance SDK using your package manager: | ||
|
||
```sh | ||
npm i @polkadot-api/sdk-governance | ||
``` | ||
|
||
Then, initialize it by passing in the `typedApi` for your chain: | ||
|
||
```ts | ||
import { createBountiesSdk } from '@polkadot-api/sdk-governance'; | ||
import { dot } from '@polkadot-api/descriptors'; | ||
import { getSmProvider } from 'polkadot-api/sm-provider'; | ||
import { chainSpec } from 'polkadot-api/chains/polkadot'; | ||
import { start } from 'polkadot-api/smoldot'; | ||
import { createClient } from 'polkadot-api'; | ||
|
||
const smoldot = start(); | ||
const chain = await smoldot.addChain({ chainSpec }); | ||
|
||
const client = createClient(getSmProvider(chain)); | ||
const typedApi = client.getTypedApi(dot); | ||
|
||
const bountiesSdk = createBountiesSdk(typedApi); | ||
``` | ||
|
||
## Get the current list of Bounties | ||
|
||
The Bounties pallet stores bounty descriptions on-chain as a separate storage query. The Bounties SDK automatically loads these descriptions when retrieving any bounty. | ||
|
||
To get the list of bounties at a given time use `getBounties`, or use `getBounty(id)` to retrieve a specific bounty by ID: | ||
|
||
```ts | ||
const bounties = await bountiesSdk.getBounties(); | ||
const bounty = await bountiesSdk.getBounty(10); | ||
``` | ||
|
||
To retrieve a bounty created from a `propose_bounty` call, use `getProposedBounty` with the result from `submit`: | ||
|
||
```ts | ||
const tx = typedApi.tx.Bounties.propose_bounty({ | ||
description: Binary.fromText("Very special bounty"), | ||
value: 100_000_000_000n, | ||
}); | ||
const result = await tx.signAndSubmit(signer); | ||
const bounty = await bountiesSdk.getProposedBounty(result); | ||
``` | ||
|
||
You can also subscribe to changes using the watch API. This provides two ways of working with it: `bounties$` returns a `Map<number, Bounty>` with all bounties, and there are also `bountyIds$` and `getBountyById$(id: number)` for cases where you want to show the list and detail separately. | ||
|
||
```ts | ||
// Map<number, Bounty> | ||
bountiesSdk.watch.bounties$.subscribe(console.log); | ||
|
||
// number[] | ||
bountiesSdk.watch.bountyIds$.subscribe(console.log); | ||
|
||
// Bounty | ||
bountiesSdk.watch.getBountyById$(5).subscribe(console.log); | ||
``` | ||
|
||
The underlying subscription to bounties and descriptions is shared among all subscribers and is automatically cleaned up when all subscribers unsubscribe. | ||
|
||
## Bounty States | ||
|
||
Bounties have many states they can be in, each with its own available operations, and some have some extra parameters. | ||
|
||
![Bounties](/bounties.png) | ||
|
||
The SDK exposes these states through a union of Bounty types, discriminated by `type`. Each type includes only the methods and parameters relevant to its status. | ||
|
||
### Proposed | ||
|
||
After a bounty is proposed, it must be approved via a referendum. Use `approveBounty()` to create the approval transaction. Submit it as part of a referendum using the [Referenda SDK](/sdks/governance/referenda): | ||
|
||
```ts | ||
const approveCall = proposedBounty.approveBounty(); | ||
const callData = await approveCall.getEncodedData(); | ||
|
||
const tx = referendaSdk.createSpenderReferenda(callData, proposedBounty.value); | ||
await tx.signAndSubmit(signer); | ||
``` | ||
|
||
You can also filter existing referenda that are already approving the bounty: | ||
|
||
```ts | ||
const referenda = await referendaSdk.getOngoingReferenda(); | ||
const approvingReferenda = await proposedBounty.findApprovingReferenda(referenda); | ||
``` | ||
|
||
Once the referendum passes, its content is removed from the chain and scheduled for enactment. The SDK can check the scheduler for these cases: | ||
|
||
```ts | ||
const scheduledApprovals = await proposedBounty.getScheduledApprovals(); | ||
// number[] which are the block number in which a change is scheduled. | ||
console.log(scheduledApprovals); | ||
``` | ||
|
||
### Approved | ||
|
||
No methods are available in the Approved state. The bounty is pending the next treasury spend period to become Funded. | ||
|
||
### Funded | ||
|
||
After funding, a new referendum must propose a curator. This state shares methods with [Proposed](#proposed) for filtering referenda and checking the scheduler. | ||
|
||
```ts | ||
const curator = "…SS58 address…"; | ||
const fee = 1_000_000; | ||
const proposeCuratorCall = fundedReferendum.proposeCurator(curator, fee); | ||
const callData = await proposeCuratorCall.getEncodedData(); | ||
|
||
const tx = referendaSdk.createSpenderReferenda(callData, fundedReferendum.value); | ||
await tx.signAndSubmit(signer); | ||
``` | ||
|
||
### CuratorProposed | ||
|
||
Has methods for `acceptCuratorRole()` and `unassignCurator()` | ||
|
||
### Active | ||
|
||
Has methods for `extendExpiry(remark: string)` and `unassignCurator()` | ||
|
||
### Pending Payout | ||
|
||
Has methods for `claim()` and `unassignCurator()`. In this case, unassign curator must also happen in a referendum. | ||
|
||
## Child Bounties | ||
|
||
Some chains support child bounties, allowing a curator to split a bounty into smaller tasks. This feature is available through a separate SDK, which requires the chain to have `ChildBounties` pallet. | ||
|
||
```ts | ||
import { createChildBountiesSdk } from '@polkadot-api/sdk-governance'; | ||
|
||
const childBountiesSdk = createChildBountiesSdk(typedApi); | ||
``` | ||
|
||
It's very similar to the bounties SDK, except that it needs a `parentId` of the parent bounty. | ||
|
||
```ts | ||
const parentId = 10; | ||
|
||
// Fetch one child bounty | ||
const childBounty = await childBountiesSdk.getChildBounty(parentId, 5); | ||
|
||
// Watch the list of child bounties | ||
childBountiesSdk.watch(parentId).bounties$.subscribe(console.log); | ||
|
||
// Watch the list of child bounty IDs | ||
childBountiesSdk.watch(parentId).bountyIds$.subscribe(console.log); | ||
|
||
// Get a specific watched child bounty | ||
childBountiesSdk.watch(parentId).getBountyById$(5).subscribe(console.log); | ||
``` | ||
|
||
Subscriptions to child bounties and descriptions are shared among subscribers for each `parentId` and are cleaned up when all subscribers unsubscribe. | ||
|
||
### States | ||
|
||
Child bounty states are simplified, and eliminates the need for referenda. The curator directly manages these bounties. | ||
|
||
![Child Bounties](/childBounties.png) | ||
|
||
The SDK exposes these states through a union of ChildBounty types, discriminated by `type`. Each type includes only the methods relevant to its status. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
# Referenda SDK | ||
|
||
The Referenda SDK provides the following features: | ||
|
||
- Abstraction over Preimages | ||
- When creating a referendum, it only generates a preimage if required by the length, significantly reducing deposit costs. | ||
- When reading a referendum, it resolves the calldata regardless of whether it's a preimage or not. | ||
- Automatic Track Selection for Spender Referenda | ||
- Automatically selects the appropriate spender track for a referendum with a given value. | ||
- Approval/Support Curves for Tracks | ||
- Provides approval/support curves as regular JavaScript functions, useful for creating charts. | ||
- Calculates the confirmation start and end blocks based on the current referendum results. | ||
|
||
## Getting Started | ||
|
||
Install the Governance SDK using your package manager: | ||
|
||
```sh | ||
npm i @polkadot-api/sdk-governance | ||
``` | ||
|
||
Then, initialize it by passing in the `typedApi` for your chain: | ||
|
||
```ts | ||
import { createReferendaSdk } from '@polkadot-api/sdk-governance'; | ||
import { dot } from '@polkadot-api/descriptors'; | ||
import { getSmProvider } from 'polkadot-api/sm-provider'; | ||
import { chainSpec } from 'polkadot-api/chains/polkadot'; | ||
import { start } from 'polkadot-api/smoldot'; | ||
import { createClient } from 'polkadot-api'; | ||
|
||
const smoldot = start(); | ||
const chain = await smoldot.addChain({ chainSpec }); | ||
|
||
const client = createClient(getSmProvider(chain)); | ||
const typedApi = client.getTypedApi(dot); | ||
|
||
const referendaSdk = createReferendaSdk(typedApi); | ||
``` | ||
|
||
Different chains have their own spender track configurations, which unfortunately are hard-coded and not available on-chain. By default, the Referenda SDK uses Polkadot's configuration. For Kusama, you can import the Kusama configuration and pass it into the options parameter: | ||
|
||
```ts | ||
import { createReferendaSdk, kusamaSpenderOrigin } from '@polkadot-api/sdk-governance'; | ||
|
||
const referendaSdk = createReferendaSdk(typedApi, { | ||
spenderOrigin: kusamaSpenderOrigin | ||
}); | ||
``` | ||
|
||
## Creating a Referendum | ||
|
||
There are multiple origins to choose from when creating a referendum, each with [specific purposes](https://wiki.polkadot.network/docs/learn-polkadot-opengov-origins#origins-and-tracks-info). | ||
|
||
For creating a referendum that requires a treasury spend, the SDK automatically selects the appropriate origin: | ||
|
||
```ts | ||
const beneficiaryAddress = "………"; | ||
const amount = 10_000_0000_000n; | ||
const spendCall = typedApi.tx.Treasury.spend({ | ||
amount, | ||
beneficiary: MultiAddress.Id(beneficiaryAddress) | ||
}); | ||
const callData = await spendCall.getEncodedData(); | ||
|
||
const tx = referendaSdk.createSpenderReferenda(callData, amount); | ||
|
||
// Submitting the transaction will create the referendum on-chain | ||
const result = await tx.signAndSubmit(signer); | ||
const referendumInfo = referendaSdk.getSubmittedReferendum(result); | ||
if (referendumInfo) { | ||
console.log("Referendum ID:", referendumInfo.index); | ||
console.log("Referendum Track:", referendumInfo.track); | ||
} | ||
``` | ||
|
||
For non-spender referenda, you need to provide the origin: | ||
|
||
```ts | ||
const remarkCall = typedApi.tx.System.remark({ | ||
remark: Binary.fromText("Make Polkadot even better") | ||
}); | ||
const callData = await remarkCall.getEncodedData(); | ||
|
||
const tx = referendaSdk.createReferenda( | ||
PolkadotRuntimeOriginCaller.Origins(GovernanceOrigin.WishForChange()), | ||
callData | ||
); | ||
|
||
const result = await tx.signAndSubmit(signer); | ||
const referendumInfo = referendaSdk.getSubmittedReferendum(result); | ||
if (referendumInfo) { | ||
console.log("Referendum ID:", referendumInfo.index); | ||
console.log("Referendum Track:", referendumInfo.track); | ||
} | ||
``` | ||
|
||
When creating a referendum, if the call is short it can be inlined directly in the referendum submit call. Otherwise, it must be registered as a preimage. The SDK automatically handles this, inlining the call if possible or creating a batch transaction to register the preimage and submit the referendum with just one transaction. | ||
|
||
## Fetching Ongoing Referenda | ||
|
||
Closed referenda are mostly removed from the chain. The Referenda SDK lists ongoing referenda based from on-chain data, or can fetch one specific by index: | ||
|
||
```ts | ||
const referenda: Array<OngoingReferendum> = await referendaSdk.getOngoingReferenda(); | ||
|
||
const referendum: OngoingReferendum | null = await referendaSdk.getOngoingReferendum(15); | ||
``` | ||
|
||
You can also subscribe to changes using the watch API. This provides two ways of working with it: `ongoingReferenda$` returns a `Map<number, OngoingReferendum>` with all referenda, and there are also `ongoingReferendaIds$` and `getOngoingReferendumById$(id: number)` for cases where you want to show the list and detail separately. | ||
|
||
```ts | ||
// Map<number, OngoingReferendum> | ||
referendaSdk.watch.ongoingReferenda$.subscribe(console.log); | ||
|
||
// number[] | ||
referendaSdk.watch.ongoingReferendaIds$.subscribe(console.log); | ||
|
||
// Bounty | ||
referendaSdk.watch.getOngoingReferendumById$(5).subscribe(console.log); | ||
``` | ||
|
||
`OngoingReferendum` provides helpful methods to interact with proposals. | ||
|
||
First of all, the proposal on a referendum can be inlined or through a preimage. `OngoingReferendum` unwraps this to get the raw call data or even the decoded call data: | ||
|
||
```ts | ||
console.log(referenda[0].proposal.rawValue); // PreimagesBounded | ||
console.log(await referenda[0].proposal.resolve()); // Binary with the call data | ||
console.log(await referenda[0].proposal.decodedCall()); // Decoded call data | ||
``` | ||
|
||
You can also check when the referendum enters or finishes the confirmation phase: | ||
|
||
```ts | ||
console.log(await referenda[0].getConfirmationStart()); // number | null | ||
console.log(await referenda[0].getConfirmationEnd()); // number | null | ||
``` | ||
|
||
Lastly, there is some useful information that's not available on-chain, but through the public forums (e.g. OpenGov or subsquare). To fetch this information, like the referendum title, you can use a [Subscan API Key](https://support.subscan.io): | ||
|
||
```ts | ||
const apiKey = "………"; | ||
console.log(await referenda[0].getDetails(apiKey)); // { title: string } | ||
``` | ||
|
||
## Accessing Track Details | ||
|
||
Referendum tracks are runtime constants with specific properties for periods, approval, and support curves. | ||
|
||
This SDK enhances the track by adding some helper functions to easily work with the curves. | ||
|
||
You can get the track from an `OngoingReferendum` through the method `referendum.getTrack()`, or you can get one by id: | ||
|
||
```ts | ||
const referendumTrack = await referendum.getTrack(); | ||
const track = await referendaSdk.getTrack(5); | ||
``` | ||
|
||
Then for both `minApproval` and `minSupport` curves, it adds functions to get the values at specific points of the curve: | ||
|
||
```ts | ||
// Raw curve data (LinearDescending, SteppedDecreasing or Reciprocal with parameters) | ||
console.log(track.curve); | ||
|
||
// Get the threshold [0-1] value when time is at 10th block. | ||
console.log(track.getThreshold(10)); | ||
|
||
// Get the first block at which the threshold 50% happens | ||
// Returns -Infinity if the curve starts at a lower threshold (meaning it has reached the threshold since the beginning) | ||
// Returns +Infinity if the curve ends at a higher threshold (meaning it will never reach the threshold) | ||
console.log(track.getBlock(0.5)); | ||
|
||
// Get Array<{ block:number, threshold: number }> needed to draw a chart. | ||
// It will have the minimum amount of datapoints needed to draw a line chart. | ||
console.log(track.getData()); | ||
``` | ||
|
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters