This document summarizes the Polkadot multisig integration for GrantFlow's milestone-based grant payments.
✅ COMPLETE:
dedotlibrary for Polkadot blockchain interactions@luno-kit/reactfor wallet connection and management@luno-kit/uifor wallet UI components- Child bounty payout system implemented
- Multisig approval workflow implemented
- Integration tests passing (59 tests)
Polkadot Stack:
- dedot (v0.16.0) - Type-safe Substrate/Polkadot client with
LegacyClient - @luno-kit/react (v0.0.8) - React hooks for wallet connection (
useApi,useConnect,useSigner) - @luno-kit/ui (v0.0.8) - Pre-built wallet selector components
Current Status: ✅ Infrastructure Complete | ⏳ Testnet Validation Pending
New Tables:
milestone_approvals- Tracks on-chain multisig execution processmultisig_signatures- Individual blockchain signatures from committee members
Enhanced Tables:
groups.settings.multisig- Committee multisig wallet configuration- Relationship:
multisig_signatures.reviewId→reviews.id(links off-chain decision to on-chain execution)
Files: /src/lib/polkadot/
client.ts- WebSocket connection to Polkadot network (Paseo testnet / mainnet)multisig.ts- Core multisig functions:initiateMultisigApproval()- First signatory publishes + votesapproveMultisigCall()- Intermediate signatories approveapproveOrExecuteMultisigCall()- Smart approval that executes when quorum is hitcreateBatchedPaymentCall()- Atomic transfer + remark
child-bounty.ts- Child bounty payout functions:getNextChildBountyId()- Query next available child bounty IDgetParentBounty()- Query parent bounty info including curatorgetParentBountyCurator()- Get curator address for a parent bountycreateChildBountyBundle()- Create 5-call atomic bundle for payoutcreatePayoutCall()- Main entry point for milestone payouts
File: /src/lib/db/writes/milestone-approvals.ts
createMilestoneApproval()- Record multisig initiationcreateMultisigSignature()- Record individual signaturesgetMilestoneApprovalWithVotes()- Fetch approval with all signaturescompleteMilestoneApproval()- Mark milestone complete after executionhasUserVoted()- Check if signatory already signed
File: /src/app/(dashboard)/dashboard/submissions/multisig-actions.ts
initiateMultisigApproval- Start multisig approval processcastMultisigVote- Record intermediate signaturesfinalizeMultisigApproval- Execute final transaction and complete milestonegetMilestoneApprovalStatus- Query current approval state
File: /src/components/providers/polkadot-provider.tsx
- React context for Polkadot wallet connections
- Supports: Polkadot.js, Talisman, SubWallet, Nova
- Auto-reconnection from localStorage
- Account selection and signer management
Committees can choose between two approval workflows:
Review Approval → Blockchain Signature → Threshold Met → Payment Executes
- Use case: High-trust committees, fast execution
- Process: Single action approves AND signs blockchain transaction
- Link:
multisig_signatures.reviewIdpoints toreviews.id
Phase 1: Review Approval → Quorum → Mark "Approved Pending Payment"
Phase 2: Initiate Payment → Signatures → Threshold Met → Payment Executes
- Use case: High-value grants, regulated environments
- Process: Discuss/approve first, then separate payment authorization
- Link: Signatures in phase 2 have
reviewId = null
Committee admins configure multisig in group settings:
interface MultisigConfig {
multisigAddress: string // Committee's multisig wallet
signatories: string[] // Member wallet addresses
threshold: number // Required signatures (e.g., 2 of 3)
approvalWorkflow: 'merged' | 'separated' // Which pattern to use
requireAllSignatories: boolean // Force all to sign vs. threshold
votingTimeoutBlocks: number // Expiry time
automaticExecution: boolean // Auto-execute on threshold
network: 'polkadot' | 'kusama' | 'paseo' // Which chain
// Child Bounty Configuration (required for payouts)
parentBountyId: number // Parent bounty ID on-chain
curatorProxyAddress: string // Curator for child bounties
}All milestone payouts use the childBounties pallet, which provides:
- Proper on-chain indexing by Subscan/Subsquare
- Atomic 5-call bundle (add → propose curator → accept → award → claim)
- Automatic curator fetching from chain in the UI
First-Signatory-Votes Pattern:
- First committee member calls
asMultiwith full call data - Transaction is published on-chain AND counts as first approval
- Deposit is locked from initiator (~20 tokens)
- Subsequent signatories use
approveAsMulti(only need call hash) - Final signatory provides full call data again to execute
Atomic Execution:
- Uses
utility.batchAll()to combine transfer + remark - All-or-nothing: both succeed or both fail
- On-chain record of milestone completion
Type Safety:
- All schemas use Zod validation
- Drizzle ORM type inference
- No
anytypes in codebase
-
MilestoneVotingPanel- Show approval progress, signature status -
PolkadotWalletSelector- Connect Polkadot wallet extensions (in header) -
MultisigConfigForm- Configure multisig settings in committee management -
SignatoryVoteList- Visual progress of multisig approvals -
PolkadotProvider- React context for wallet management - Integration with reviewer submission view (shows voting panel for approved milestones)
- Database schema and server actions complete
- Workflow pattern support (merged/separated)
- Committee management UI for multisig configuration
- Install
dedotand@luno-kitpackages - Configure Polkadot client with
LegacyClientfrom dedot - Implement multisig functions in
src/lib/polkadot/multisig.ts:initiateMultisigApproval()approveOrExecuteMultisigCall()finalizeMultisigCall()
- Implement child bounty functions in
src/lib/polkadot/child-bounty.ts - Test actual blockchain transactions on Paseo testnet
- Create test multisig wallet on Paseo with 2 signatories
- Test merged workflow end-to-end with real wallet signatures
- Test separated workflow end-to-end
- Validate transaction cost calculations
- Error handling for failed transactions
- Edge cases: expired approvals, rejected signatures, insufficient balance
- Query pending multisigs from blockchain (
queryPendingMultisigs) - Cancel/replace pending approvals
- Batch multiple milestone payments
- Historical signature analytics
- Gas estimation before transaction
- Notification system for signature requests
The project uses the following Polkadot-related packages:
{
"dependencies": {
"dedot": "^0.16.0", // Polkadot client and utilities
"@luno-kit/react": "^0.0.8", // React hooks for wallet connection
"@luno-kit/ui": "^0.0.8" // UI components for wallet selection
},
"devDependencies": {
"@dedot/chaintypes": "^0.171.0" // Type definitions for chains
}
}The client is configured in /src/lib/polkadot/lunokit.ts:
import { createConfig } from '@luno-kit/react'
import { polkadot, kusama, paseo } from '@luno-kit/react/chains'
import { talisman, subwallet, nova, polkadotJs } from '@luno-kit/react/connectors'
export const config = createConfig({
chains: [polkadot, kusama, paseo],
connectors: [talisman(), subwallet(), nova(), polkadotJs()],
})import { useApi, useSigner, useConnect } from '@luno-kit/react'
import type { LegacyClient } from 'dedot'
function MyComponent() {
const { api: client } = useApi('paseo')
const signer = useSigner()
const { connect, isConnected } = useConnect()
// Use client for queries
const balance = await client.query.system.account(address)
// Use client for transactions
const tx = client.tx.multisig.asMulti(...)
await tx.signAndSend(signer)
}Resources:
Run migration to create new tables:
pnpm db:pushThis will create:
milestone_approvalstablemultisig_signaturestable- New enum types:
approval_status,signature_type
Before production:
- Test wallet connections (all supported extensions)
- Test merged workflow end-to-end on testnet
- Test separated workflow end-to-end on testnet
- Verify transaction costs and gas estimates
- Test threshold edge cases (2/3, 3/5, unanimous)
- Test rejection/cancellation flows
- Verify proper error handling and rollback
- Load test with multiple concurrent approvals
See detailed documentation:
/src/lib/polkadot/README.md- Workflow patterns and examples- Polkadot multisig docs: https://wiki.polkadot.com/learn/learn-guides-accounts-multisig/
- dedot docs: https://docs.dedot.dev/
- Separate tables for reviews vs signatures - Reviews are off-chain decisions, signatures are on-chain cryptographic proofs
- Optional reviewId link - Connects off-chain vote to on-chain signature in merged workflow
- Workflow configuration - Committees choose their own pattern based on trust/requirements
- First-signatory-votes - Follows Polkadot best practice of publishing + voting in one transaction
- Batched calls - Atomic transfer + remark ensures data integrity
Existing Code Modified:
- ❌ None yet (all new code, no modifications to existing)
Existing Code Will Need Updates:
src/components/milestone-completion-form.tsx- Add multisig optionsrc/components/milestone-status.tsx- Show blockchain statussrc/app/(dashboard)/dashboard/committees/[id]/manage/page.tsx- Multisig config UIsrc/components/review/milestone-review-dialog.tsx- Integrate signature flow
// Committee member approves milestone
async function approveMilestone() {
// 1. Create review (off-chain)
const review = await submitReview({ vote: 'approve' })
// 2. Connect wallet and sign (on-chain)
const { selectedSigner } = usePolkadot()
// 3. Initiate or join multisig
if (!existingApproval) {
await initiateMultisigApproval({
approvalWorkflow: 'merged',
reviewId: review.id
})
} else {
await castMultisigVote({
signatureType: 'signed',
reviewId: review.id
})
}
}// Phase 1: Approve (off-chain)
async function approveMilestone() {
await submitReview({ vote: 'approve' })
// Quorum check happens automatically
}
// Phase 2: Execute payment (on-chain)
async function executePayment() {
const { selectedSigner } = usePolkadot()
await initiateMultisigApproval({
approvalWorkflow: 'separated',
reviewId: null // No review link in separated mode
})
}✅ Complete:
- Database schema (milestone_approvals, multisig_signatures tables)
- Server actions (initiate, vote, finalize)
- React components (MilestoneVotingPanel, MultisigConfigForm, WalletSelector)
- LunoKit Provider context and wallet integration
- UI integration in reviewer submission view
- Committee management configuration (with parentBountyId and curatorProxyAddress)
- Both workflow patterns (merged/separated) supported
- Seed data with multisig-enabled committee
- ✨ dedot client for blockchain interactions
- ✨ @luno-kit hooks for wallet connection
- ✨ Child bounty payout system
- ✨ Integration tests (59 tests passing)
⏳ Pending:
- Test on Paseo testnet with real multisig wallet and parent bounty
- Full end-to-end testing of child bounty payout flow
# Reset and seed database with multisig configuration
pnpm db:reset- Login as
reviewer1@test.com(password:reviewer123) - Navigate to Infrastructure Committee
- View an approved milestone
- Connect Polkadot wallet (ensure you have Paseo testnet tokens)
- Sign the multisig transaction
- View on Paseo block explorer: https://paseo.subscan.io/
- Check multisig status on-chain
- Track child bounties via Subsquare
# Run all tests
pnpm test
# Run with watch mode
pnpm test:watch
# Run specific test file
pnpm test -- child-bounty.test.ts