Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MultiSig Implementation #417

Merged
merged 22 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions examples/bin/extrinsic_demo.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import 'dart:typed_data';

import 'package:convert/convert.dart';
import 'package:polkadart/polkadart.dart'
show
AuthorApi,
Extrinsic,
ExtrinsicPayload,
Provider,
SignatureType,
SigningPayload,
Expand Down Expand Up @@ -39,11 +41,11 @@ Future<void> main(List<String> arguments) async {
final keyring = await KeyPair.sr25519.fromMnemonic(
"resource mirror lecture smooth midnight muffin position cup pepper fruit vanish also//0"); // This is a random key

final publicKey = hex.encode(keyring.publicKey.bytes);
final publicKey = keyring.publicKey.bytes;
print('Public Key: $publicKey');
final dest = $MultiAddress().id(hex.decode(publicKey));
final dest = $MultiAddress().id(publicKey);
final runtimeCall = api.tx.balances.transferAll(dest: dest, keepAlive: true);
final encodedCall = hex.encode(runtimeCall.encode());
final encodedCall = runtimeCall.encode();
print('Encoded call: $encodedCall');

final payloadToSign = SigningPayload(
Expand All @@ -65,10 +67,10 @@ Future<void> main(List<String> arguments) async {
final hexSignature = hex.encode(signature);
print('Signature: $hexSignature');

final extrinsic = Extrinsic(
signer: publicKey,
final extrinsic = ExtrinsicPayload(
signer: Uint8List.fromList(publicKey),
method: encodedCall,
signature: hexSignature,
signature: signature,
eraPeriod: 64,
blockNumber: blockNumber,
nonce: 0,
Expand Down
96 changes: 96 additions & 0 deletions packages/polkadart/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,102 @@ void main() async {
}
```

## Multisig

### Initiate Multisig (Create and Fund)

```dart
final provider = Provider.fromUri(Uri.parse('wss://westend-rpc.polkadot.io'));

// Signatory 1
final keypairS1 = await keyring.KeyPair.sr25519.fromUri('//keypairS1');

// Signatory 2
final signatory2Address = '5Cp4.......';

// Signatory 3
final signatory3Address = '5Dt2.......';

// Recipient's Address
final recipientAddress = '5Fq3.......';

///
/// Create and Fund Multisig
final multiSigResponse = await Multisig.createAndFundMultisig(
depositorKeyPair: keypairS1,
otherSignatoriesAddressList: [signatory2Address, signatory3Address],
threshold: 2,
recipientAddress: recipientAddress,
amount: BigInt.parse('7000000000000'), // 7 WND ~ (tokenDecimals: 12)
provider: provider,
);

// Json Response for forwarding to other signatories.
final json = multiSigResponse.toJson();

```

### ApproveAsMulti
#### Used to approve the multisig call and then it waits for other singatories to approve.

```dart
// Signatory 2
final keypairS2 = await keyring.KeyPair.sr25519.fromUri('//keypairS2');

// Json for forwarding to other signatories.
final json = multiSigResponse.toJson();

// Assuming keypairS2 is the first signatory who is approving.
final localResponse = MultisigResponse.fromJson(json);

// Waiting for approx 15 seconds for the transaction to be included in the block.
await Future.delayed(Duration(seconds: 15));

// Approve this call by keypairS2 and wait on further approval.
await localResponse.approveAsMulti(provider, keypairS2);

```

### AsMulti
#### Used to do the final approval of the multisig call and execute the transaction if the threshold is met.

```dart
// Signatory 3
// Assuming keypairS3 is the first signatory who is approving.
final keypairS3 = await keyring.KeyPair.sr25519.fromUri('//keypairS3');

// Json for forwarding to other signatories.
final json = multiSigResponse.toJson();

final localResponse = MultisigResponse.fromJson(json);

// Waiting for approx 15 seconds for the transaction to be included in the block.
await Future.delayed(Duration(seconds: 15));

// Approve this call by keypairS3 and execute the transaction if the threshold is met
await localResponse.asMulti(provider, keypairS3);
```

### cancelAsMulti
#### Used to do cancel the multisig call.

```dart
// Signatory 1
// Assuming keypairS1 is the first signatory who is approving.
final keypairS1 = await keyring.KeyPair.sr25519.fromUri('//keypairS1');

// Json for forwarding to other signatories.
final json = multiSigResponse.toJson();

final localResponse = MultisigResponse.fromJson(json);

// Waiting for approx 15 seconds for the transaction to be included in the block.
await Future.delayed(Duration(seconds: 15));

// Approve this call by keypairS1 and execute the transaction if the threshold is met
await localResponse.asMulti(provider, keypairS1);
```

## Tutorials

Looking for tutorials to get started? Look at [example](./example) for guides on how to use the API to make queries and submit transactions.
70 changes: 70 additions & 0 deletions packages/polkadart/example/multisig_example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import 'package:polkadart/multisig/multisig_base.dart';
import 'package:polkadart/polkadart.dart';
import 'package:polkadart_keyring/polkadart_keyring.dart' as keyring;

void main() async {
//
// Signatories: keypairS1, keypairS2, keypairS3
final keypairS1 = await keyring.KeyPair.sr25519.fromUri('//keypairS1');
keypairS1.ss58Format = 42;

final keypairS2 = await keyring.KeyPair.sr25519.fromUri('//keypairS2');
keypairS2.ss58Format = 42;

final keypairS3 = await keyring.KeyPair.sr25519.fromUri('//keypairS3');
keypairS3.ss58Format = 42;

// Recipient: keypairR
final keypairR = await keyring.KeyPair.sr25519.fromUri('//keypairR');
keypairR.ss58Format = 42;

final provider = Provider.fromUri(Uri.parse('wss://westend-rpc.polkadot.io'));

///
/// Create and Fund Multisig
final multiSigResponse = await Multisig.createAndFundMultisig(
depositorKeyPair: keypairS1,
otherSignatoriesAddressList: [keypairS2.address, keypairS3.address],
threshold: 2,
recipientAddress: keypairR.address,
amount: BigInt.parse('711${'0' * 10}'), // 7.11 WND
provider: provider,
);

// Json for forwarding to other signatories.
final json = multiSigResponse.toJson();

{
// Assuming keypairS2 is the first signatory who is approving.
final localResponse = MultisigResponse.fromJson(json);
// Approve this call by keypairS2 and forward for further approval.
await Future.delayed(Duration(seconds: 15));
await localResponse.approveAsMulti(provider, keypairS2);
}

/**
* Although keypairS3 can call `approveAsMulti` to approve this call which will then
* wait for `keypairS1` to approve via `asMulti` call.
*
* In this case:
* The last signatory (`keypairS1`) will not be able to call `approveAsMulti` as it will throw FinalApprovalException.
*
* So, `keypairS1` will have to call `asMulti` to approve this call.
*/

{
// Assuming keypairS3 is the second signatory who is approving.
final localResponse = MultisigResponse.fromJson(json);
// Execute this call by keypairS3 approval.
await Future.delayed(Duration(seconds: 15));
await localResponse.asMulti(provider, keypairS3);
}

// // Cancel this call by keypairS1
//
// final localResponse = MultisigResponse.fromJson(json);
//
// // Cancel this call by keypairS1
// await Future.delayed(Duration(seconds: 15));
// await multiSigResponse.cancelAsMulti(provider, keypairS1);
}
4 changes: 4 additions & 0 deletions packages/polkadart/lib/apis/apis.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import 'dart:typed_data' show Uint8List;
import 'dart:async' show Future, StreamSubscription;
import 'package:convert/convert.dart' show hex;
import 'package:polkadart/polkadart.dart';
import 'dart:typed_data';

import 'package:convert/convert.dart';

part './author.dart';
part './chain.dart';
part './state.dart';
part './system.dart';
41 changes: 41 additions & 0 deletions packages/polkadart/lib/apis/chain.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
part of apis;

/// Substrate chain API
class ChainApi<P extends Provider> {
final P _provider;

ChainApi(this._provider);

/// Returns the BlockHash for the given block number.
///
/// If no block number is provided, it will return the hash of the latest block at chain height.
Future<BlockHash> getBlockHash({int? blockNumber}) async {
final List<dynamic> params = <dynamic>[];
if (blockNumber != null) {
params.add(blockNumber);
}

final response = await _provider.send('chain_getBlockHash', params);

if (response.error != null) {
throw Exception(response.error.toString());
}

return Uint8List.fromList(
hex.decode((response.result as String).substring(2)));
}

/// Get the latest block number or specific block number by BlockHash.
Future<int> getChainHeader({BlockHash? at}) async {
final List<String> params = <String>[];
if (at != null) {
params.add('0x${hex.encode(at)}');
}
final response = await _provider.send('chain_getHeader', params);

if (response.error != null) {
throw Exception(response.error.toString());
}
return int.parse(response.result['number']!);
}
}
39 changes: 39 additions & 0 deletions packages/polkadart/lib/extrinsic/abstract_payload.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'dart:typed_data';

abstract class Payload {
final Uint8List method; // Call
final int blockNumber;
final int eraPeriod; // CheckMortality
final int nonce; // CheckNonce
final dynamic tip; // ChargeTransactionPayment
final int? assetId; // ChargeAssetTxPayment

const Payload({
required this.method,
required this.blockNumber,
required this.eraPeriod,
required this.nonce,
required this.tip,
this.assetId,
});

toEncodedMap(dynamic registry);

bool usesChargeAssetTxPayment(dynamic registry) {
if (registry.getSignedExtensionTypes() is Map) {
return (registry.getSignedExtensionTypes() as Map)
.containsKey('ChargeAssetTxPayment');
}
return (registry.getSignedExtensionTypes() as List)
.contains('ChargeAssetTxPayment');
}

String maybeAssetIdEncoded(dynamic registry) {
if (usesChargeAssetTxPayment(registry)) {
// '00' and '01' refer to rust's Option variants 'None' and 'Some'.
return assetId != null ? '01${assetId!.toRadixString(16)}' : '00';
} else {
return '';
}
}
}
Loading
Loading