跳到主要内容

多重签名交易

Polkadot 多重签名交易需要多个签名者共同授权才能执行。本指南将详细介绍多重签名交易的完整流程,包括发起、批准和执行。

多重签名交易概述

多重签名交易具有以下特点:

  • 多步骤流程: 发起 → 批准 → 执行
  • 阈值控制: 需要达到指定数量的批准
  • 时间点管理: 使用时间点跟踪交易状态
  • 费用分摊: 所有签名者分担交易费用
  • 支持多种操作: 转账、质押、质押额外代币、提名等

交易流程概览

发起多重签名交易

基本发起流程

async function initiateMultisigTransaction(api, multisig, call, proposer) {
try {
console.log('=== Initiating Multisig Transaction ===');

// 1. 获取多重签名信息
const info = await api.query.multisig.multisigs(multisig.address, call.method.hash);

// 2. 设置时间点(首次批准为 null)
const timepoint = info.isSome ? info.unwrap().when : null;

// 3. 准备其他签名者列表(不包括发起者)
const otherSignatories = multisig.signatories
.filter(addr => addr !== proposer.address)
.sort();

console.log('Transaction call hash:', call.method.hash);
console.log('Timepoint:', timepoint);
console.log('Other signatories:', otherSignatories);

// 4. 发起多重签名交易
const txHash = await api.tx.multisig
.approveAsMulti(
multisig.threshold,
otherSignatories,
timepoint,
call.method.hash,
api.consts.system.maximumBlockWeight
)
.signAndSend(proposer, ({ status, events }) => {
if (status.isInBlock) {
console.log(`Transaction included in block ${status.asInBlock}`);
} else if (status.isFinalized) {
console.log(`Transaction finalized in block ${status.asFinalized}`);

// 处理事件
events.forEach(({ event: { data, method, section } }) => {
if (section === 'multisig') {
console.log(`Multisig event: ${method}`, data.toString());
}
});
}
});

console.log('✅ Multisig transaction initiated successfully');
console.log('Transaction hash:', txHash);

return txHash;

} catch (error) {
console.error('❌ Failed to initiate multisig transaction:', error);
throw error;
}
}

创建交易调用

async function createTransactionCall(api, type, params) {
let call;

switch (type) {
case 'transfer':
call = api.tx.balances.transfer(params.to, params.amount);
break;

case 'stake':
call = api.tx.staking.bond(params.controller, params.amount, params.payee);
break;

case 'bondExtra':
call = api.tx.staking.bondExtra(params.amount);
break;

case 'unstake':
call = api.tx.staking.unbond(params.amount);
break;

case 'vote':
call = api.tx.democracy.vote(params.refIndex, params.vote);
break;

default:
throw new Error(`Unsupported transaction type: ${type}`);
}

console.log(`Created ${type} call:`, call.method.toHuman());
console.log('Call hash:', call.method.hash);

return call;
}

### 质押额外代币多重签名

对于质押额外代币操作,需要特别注意调用格式:

```javascript
// 创建质押额外代币调用
const call = api.tx.staking.bondExtra(amount);

// approveAsMulti 使用 call.method.hash
const callHash = call.method.hash;

// asMulti 使用 call.method.toHex()
const callHex = call.method.toHex();

详细实现请参考 质押额外代币多重签名

质押和提名验证人多重签名

对于质押和提名验证人的组合操作,使用批量交易:

// 创建质押和提名调用
const transactions = [
api.tx.staking.bond(MULTISIG, amount, 'Staked'),
api.tx.staking.nominate(targets),
];

// 使用 utility.batch 打包
const call = api.tx.utility.batch(transactions);

// 注意:执行时权重需要乘以2
const weight = MAX_WEIGHT * 2;

详细实现请参考 质押和提名验证人多重签名

提名验证人多重签名

对于单独的提名验证人操作:

// 创建提名验证人调用
const call = api.tx.staking.nominate(targets);

// approveAsMulti 使用 call.method.hash
const callHash = call.method.hash;

// asMulti 使用 call.method.toHex()
const callHex = call.method.toHex();

详细实现请参考 提名验证人多重签名

// 使用示例
const transferCall = await createTransactionCall(api, 'transfer', {
to: '5F1GPrYp5Q32Hr3hczZXoJCTxNhuQxsBbABtSjDnVK9Fn9dZ',
amount: 1000000000000 // 1 WND
});

批准多重签名交易

批准流程

async function approveMultisigTransaction(api, multisig, call, approver) {
try {
console.log('=== Approving Multisig Transaction ===');

// 1. 获取多重签名信息
const info = await api.query.multisig.multisigs(multisig.address, call.method.hash);

if (info.isNone) {
throw new Error('No pending multisig transaction found');
}

const multisigInfo = info.unwrap();
const timepoint = multisigInfo.when;

console.log('Found pending transaction');
console.log('Timepoint:', timepoint.toHuman());
console.log('Current approvals:', multisigInfo.approvals.map(addr => addr.toString()));

// 2. 检查是否已经批准
if (multisigInfo.approvals.some(addr => addr.eq(approver.address))) {
console.log('⚠️ Transaction already approved by this signatory');
return null;
}

// 3. 准备其他签名者列表
const otherSignatories = multisig.signatories
.filter(addr => addr !== approver.address)
.sort();

// 4. 批准交易
const txHash = await api.tx.multisig
.approveAsMulti(
multisig.threshold,
otherSignatories,
timepoint,
call.method.hash,
api.consts.system.maximumBlockWeight
)
.signAndSend(approver, ({ status, events }) => {
if (status.isInBlock) {
console.log(`Approval included in block ${status.asInBlock}`);
} else if (status.isFinalized) {
console.log(`Approval finalized in block ${status.asFinalized}`);
}
});

console.log('✅ Multisig transaction approved successfully');
console.log('Approval hash:', txHash);

return txHash;

} catch (error) {
console.error('❌ Failed to approve multisig transaction:', error);
throw error;
}
}

检查批准状态

async function checkApprovalStatus(api, multisigAddress, callHash) {
try {
const info = await api.query.multisig.multisigs(multisigAddress, callHash);

if (info.isSome) {
const multisigInfo = info.unwrap();

const status = {
timepoint: multisigInfo.when.toHuman(),
deposit: multisigInfo.deposit.toHuman(),
depositor: multisigInfo.depositor.toString(),
approvals: multisigInfo.approvals.map(addr => addr.toString()),
approvalCount: multisigInfo.approvals.length
};

console.log('=== Multisig Transaction Status ===');
console.log('Timepoint:', status.timepoint);
console.log('Deposit:', status.deposit);
console.log('Depositor:', status.depositor);
console.log('Approvals:', status.approvals);
console.log('Approval count:', status.approvalCount);

return status;

} else {
console.log('No pending multisig transaction found');
return null;
}

} catch (error) {
console.error('Failed to check approval status:', error);
throw error;
}
}

执行多重签名交易

执行流程

async function executeMultisigTransaction(api, multisig, call, executor) {
try {
console.log('=== Executing Multisig Transaction ===');

// 1. 获取多重签名信息
const info = await api.query.multisig.multisigs(multisig.address, call.method.hash);

if (info.isNone) {
throw new Error('No pending multisig transaction found');
}

const multisigInfo = info.unwrap();
const timepoint = multisigInfo.when;

// 2. 检查是否达到阈值
if (multisigInfo.approvals.length < multisig.threshold) {
throw new Error(`Insufficient approvals: ${multisigInfo.approvals.length}/${multisig.threshold}`);
}

console.log('Threshold reached, executing transaction');
console.log('Timepoint:', timepoint.toHuman());

// 3. 准备其他签名者列表
const otherSignatories = multisig.signatories
.filter(addr => addr !== executor.address)
.sort();

// 4. 执行交易
const txHash = await api.tx.multisig
.asMulti(
multisig.threshold,
otherSignatories,
timepoint,
call.method.toHex(),
false, // 不存储调用
api.consts.system.maximumBlockWeight
)
.signAndSend(executor, ({ status, events }) => {
if (status.isInBlock) {
console.log(`Execution included in block ${status.asInBlock}`);
} else if (status.isFinalized) {
console.log(`Execution finalized in block ${status.asFinalized}`);

// 处理执行事件
events.forEach(({ event: { data, method, section } }) => {
console.log(`Event: ${section}.${method}`, data.toString());
});
}
});

console.log('✅ Multisig transaction executed successfully');
console.log('Execution hash:', txHash);

return txHash;

} catch (error) {
console.error('❌ Failed to execute multisig transaction:', error);
throw error;
}
}

取消多重签名交易

取消流程

async function cancelMultisigTransaction(api, multisig, call, canceller) {
try {
console.log('=== Cancelling Multisig Transaction ===');

// 1. 获取多重签名信息
const info = await api.query.multisig.multisigs(multisig.address, call.method.hash);

if (info.isNone) {
throw new Error('No pending multisig transaction found');
}

const multisigInfo = info.unwrap();
const timepoint = multisigInfo.when;

// 2. 准备其他签名者列表
const otherSignatories = multisig.signatories
.filter(addr => addr !== canceller.address)
.sort();

// 3. 取消交易
const txHash = await api.tx.multisig
.cancelAsMulti(
multisig.threshold,
otherSignatories,
timepoint,
call.method.hash
)
.signAndSend(canceller, ({ status }) => {
if (status.isInBlock) {
console.log(`Cancellation included in block ${status.asInBlock}`);
} else if (status.isFinalized) {
console.log(`Cancellation finalized in block ${status.asFinalized}`);
}
});

console.log('✅ Multisig transaction cancelled successfully');
console.log('Cancellation hash:', txHash);

return txHash;

} catch (error) {
console.error('❌ Failed to cancel multisig transaction:', error);
throw error;
}
}

完整示例

const { ApiPromise, WsProvider, Keyring } = require('@polkadot/api');
const { createKeyMulti, encodeAddress, sortAddresses } = require('@polkadot/util-crypto');
const { formatBalance } = require('@polkadot/util');

async function completeMultisigExample() {
// 连接到网络
const wsProvider = new WsProvider('wss://westend-rpc.polkadot.io');
const api = await ApiPromise.create({ provider: wsProvider });

// 设置余额格式
formatBalance.setDefaults({
unit: 'WND',
decimals: 12
});

try {
// 等待网络就绪
await api.isReady;

// 创建密钥环
const keyring = new Keyring({ ss58Format: 42, type: 'sr25519' });

// 创建测试账户
const alice = keyring.addFromUri('//Alice');
const bob = keyring.addFromUri('//Bob');
const charlie = keyring.addFromUri('//Charlie');

console.log('Test accounts created:');
console.log('Alice:', alice.address);
console.log('Bob:', bob.address);
console.log('Charlie:', charlie.address);

// 创建多重签名账户
const signatories = [alice.address, bob.address, charlie.address];
const threshold = 2;

const sortedSignatories = sortAddresses(signatories);
const multiAddress = createKeyMulti(sortedSignatories, threshold);
const multisigAddress = encodeAddress(multiAddress, 42);

const multisig = {
address: multisigAddress,
threshold,
signatories: sortedSignatories
};

console.log('\nMultisig account created:');
console.log('Address:', multisig.address);
console.log('Threshold:', multisig.threshold);
console.log('Signatories:', multisig.signatories);

// 创建转账调用
const recipient = '5F1GPrYp5Q32Hr3hczZXoJCTxNhuQxsBbABtSjDnVK9Fn9dZ';
const amount = 1000000000000; // 1 WND

const call = api.tx.balances.transfer(recipient, amount);
console.log('\nTransfer call created:', call.method.toHex());

// 步骤 1: Alice 发起多重签名交易
console.log('\n=== Step 1: Alice initiates the transaction ===');
await initiateMultisigTransaction(api, multisig, call, alice);

// 等待交易确认
await new Promise(resolve => setTimeout(resolve, 5000));

// 检查状态
await checkApprovalStatus(api, multisig.address, call.method.hash);

// 步骤 2: Bob 批准交易
console.log('\n=== Step 2: Bob approves the transaction ===');
await approveMultisigTransaction(api, multisig, call, bob);

// 等待交易确认
await new Promise(resolve => setTimeout(resolve, 5000));

// 检查状态
await checkApprovalStatus(api, multisig.address, call.method.hash);

// 步骤 3: Charlie 执行交易
console.log('\n=== Step 3: Charlie executes the transaction ===');
await executeMultisigTransaction(api, multisig, call, charlie);

// 等待执行确认
await new Promise(resolve => setTimeout(resolve, 5000));

// 检查最终状态
const finalStatus = await checkApprovalStatus(api, multisig.address, call.method.hash);
console.log('\nFinal status:', finalStatus ? 'Completed' : 'No pending transaction');

} catch (error) {
console.error('Multisig example failed:', error);
} finally {
await api.disconnect();
}
}

// 运行示例
completeMultisigExample().catch(console.error);

最佳实践

  1. 状态监控: 始终监控交易状态和批准进度
  2. 错误处理: 处理所有可能的错误情况
  3. 时间管理: 及时处理待确认的交易
  4. 费用考虑: 考虑多重签名操作的额外费用
  5. 安全验证: 验证所有签名者的身份

相关功能

除了基本的转账多重签名,Polkadot 还支持其他类型的多重签名操作:

常见问题

Q: 多重签名交易有有效期吗?

A: 没有固定的有效期,但建议及时处理待确认的交易。

Q: 可以部分执行多重签名交易吗?

A: 不可以,必须达到阈值才能执行。

Q: 如何知道交易是否已经批准?

A: 使用 checkApprovalStatus 函数查询当前状态。

Q: 多重签名交易失败怎么办?

A: 可以取消交易或重新发起,押金会退还给发起者。