Skip to content

Commit

Permalink
improve transaction to multi uuid receiver (#125)
Browse files Browse the repository at this point in the history
* add multisigs api

* improve send to multi receiver

* update readme
  • Loading branch information
boyan01 authored Jan 15, 2024
1 parent 997e1d0 commit d343b76
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 29 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 1.1.0

* improve transaction to multi uuid receiver
* add transaction to MixAddress

## 1.0.4

* make `getOutputs()` members parameter nullable
Expand Down
2 changes: 1 addition & 1 deletion example/common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ late Client client = Client(
privateKey: privateKey,
sessionId: botSessionId,
userId: botUserId,
);
)..dio.options.extra['retry'] = true;

const tronUSDT = 'b91e18ff-a9ae-3dc7-8679-e935d9a4b34b';
const cnb = '965e5c6e-434c-3fa9-b780-c50f43cd955c';
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ packages:
path: ".."
relative: true
source: path
version: "1.0.4"
version: "1.1.0"
path:
dependency: transitive
description:
Expand Down
3 changes: 2 additions & 1 deletion example/safe_transfer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ Future<void> main() async {
);
print('cnb balance: ${balance}');

// transaction to one user
await client.utxoApi.transactionToUser(
userId: 'cfb018b0-eaf7-40ec-9e07-28a5158f1269',
receiverIds: ['cfb018b0-eaf7-40ec-9e07-28a5158f1269'],
amount: '0.0001',
asset: Token.cnb.assetId,
spendKey: hex.encode(spendKey.bytes),
Expand Down
67 changes: 64 additions & 3 deletions lib/src/api/utxo_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,35 @@ class UtxoApi {
TransactionResponse.fromJson,
);

Future<MixinResponse<TransactionResponse>> getMultisigs(String requestId) =>
MixinResponse.request<TransactionResponse>(
dio.get(
'/safe/multisigs/$requestId',
),
TransactionResponse.fromJson,
);

Future<MixinResponse<TransactionResponse>> signTransactionMultisigs(
String requestId,
TransactionRequest request,
) =>
MixinResponse.request<TransactionResponse>(
dio.post(
'/safe/multisigs/$requestId/sign',
data: request.toJson(),
),
TransactionResponse.fromJson,
);

Future<MixinResponse<TransactionResponse>> unlockTransactionMultisigs(
String requestId) =>
MixinResponse.request<TransactionResponse>(
dio.post(
'/safe/multisigs/$requestId/unlock',
),
TransactionResponse.fromJson,
);

Future<String> assetBalance({
required String assetId,
required int threshold,
Expand Down Expand Up @@ -219,6 +248,7 @@ class UtxoApi {
required String asset,
required int threshold,
required Decimal desiredAmount,
List<String> members = const [],
}) async {
int? latestSequence;
final outputs = <SafeUtxoOutput>[];
Expand All @@ -229,6 +259,7 @@ class UtxoApi {
threshold: threshold,
asset: asset,
state: OutputState.unspent.name,
members: members,
limit: limit,
offset: latestSequence == null ? null : latestSequence + 1,
))
Expand Down Expand Up @@ -264,29 +295,38 @@ class UtxoApi {

/// Send a tx to mixin user.
///
/// [userId] destination user uuid
/// [receiverIds] destination user uuid list
/// [senderIds] sender user uuid list
///
/// [spendKey] spend key hex
///
Future<List<TransactionResponse>> transactionToUser({
required String userId,
required List<String> receiverIds,
required String amount,
required String asset,
required String spendKey,
List<String> senderIds = const [],
int threshold = 1,
String? memo,
}) async {
assert(receiverIds.isNotEmpty, 'receiverIds is empty');
receiverIds = receiverIds.toList()..sort();

final (utxos, change) = await _getEnoughOutputsForTransaction(
asset: asset,
threshold: threshold,
desiredAmount: Decimal.parse(amount),
members: senderIds,
);

final recipients = [
UserTransactionRecipient(
members: [userId],
members: receiverIds,
threshold: threshold,
amount: amount,
),
if (change > Decimal.zero)
// change
UserTransactionRecipient(
members: utxos[0].receivers,
threshold: utxos[0].receiversThreshold,
Expand Down Expand Up @@ -607,4 +647,25 @@ class UtxoApi {
return sentTx.data;
}
}

Future<List<TransactionResponse>> transactionToMixAddress({
required MixAddress address,
required String amount,
required String spendKey,
required String asset,
}) async {
assert(address.members.isNotEmpty, 'members is empty');
switch (address.memberType) {
case MixMemberType.uuid:
return transactionToUser(
receiverIds: address.members,
amount: amount,
asset: asset,
spendKey: spendKey,
);
case MixMemberType.xin:
assert(address.members.length == 1, 'members length is not 1');
throw UnimplementedError();
}
}
}
48 changes: 30 additions & 18 deletions lib/src/util/address.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const kMixAddressVersion = 2;
const kMainAddressPrefix = 'XIN';
const kMixAddressPrefix = 'MIX';

enum _MemberType {
enum MixMemberType {
xin,
uuid,
}
Expand Down Expand Up @@ -55,11 +55,29 @@ String getMainnetAddressFromPublic(Uint8List pub) {
}

class MixAddress {
MixAddress({
MixAddress._private({
required this.members,
required this.threshold,
required this.memberType,
});

factory MixAddress.fromUuid(List<String> members, int threshold) =>
MixAddress._private(
members: members,
threshold: threshold,
memberType: MixMemberType.uuid,
);

factory MixAddress.fromMainNetMixAddress(
List<String> members,
int threshold,
) =>
MixAddress._private(
members: members,
threshold: threshold,
memberType: MixMemberType.xin,
);

static MixAddress? tryParse(String address) {
try {
return parse(address);
Expand Down Expand Up @@ -105,34 +123,28 @@ class MixAddress {

final memberData = Uint8List.sublistView(payload, 3);
final members = <String>[];

if (memberData.length == total * 16) {
for (var i = 0; i < total; i++) {
final id = Uuid.unparse(memberData, offset: i * 16);
members.add(id);
}
return MixAddress(
members: members,
threshold: threshold,
);
}

if (memberData.length == total * 64) {
return MixAddress.fromUuid(members, threshold);
} else if (memberData.length == total * 64) {
for (var i = 0; i < total; i++) {
final pub = Uint8List.sublistView(memberData, i * 64, (i + 1) * 64);
final address = getMainnetAddressFromPublic(pub);
members.add(address);
}
return MixAddress(
members: members,
threshold: threshold,
);
return MixAddress.fromMainNetMixAddress(members, threshold);
}

throw Exception('invalid member data. $memberData');
}

final List<String> members;
final int threshold;
final MixMemberType memberType;

String toAddress() {
if (members.length > 255) {
Expand All @@ -149,12 +161,12 @@ class MixAddress {
]);

final memberData = <Uint8List>[];
_MemberType? type;
MixMemberType? type;
for (final member in members) {
if (member.startsWith(kMainAddressPrefix)) {
if (type == null) {
type = _MemberType.xin;
} else if (type != _MemberType.xin) {
type = MixMemberType.xin;
} else if (type != MixMemberType.xin) {
throw Exception('invalid member type. $member');
}
final pub = getPublicFromMainnetAddress(member);
Expand All @@ -164,8 +176,8 @@ class MixAddress {
memberData.add(pub);
} else {
if (type == null) {
type = _MemberType.uuid;
} else if (type != _MemberType.uuid) {
type = MixMemberType.uuid;
} else if (type != MixMemberType.uuid) {
throw Exception('invalid member type. $member');
}
try {
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: mixin_bot_sdk_dart
description: Mixin Messenger API for Dart/Flutter, build decentrialized applications on Mixin
version: 1.0.4
version: 1.1.0
homepage: https://github.com/MixinNetwork/mixin_bot_sdk_dart

environment:
Expand Down
8 changes: 4 additions & 4 deletions test/util/address_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'package:test/test.dart';
void main() {
test('test 1', () {
final members = ['67a87828-18f5-46a1-b6cc-c72a97a77c43'];
final address = MixAddress(members: members, threshold: 1).toAddress();
final address = MixAddress.fromUuid(members, 1).toAddress();
expect('MIX3QEeg1WkLrjvjxyMQf6Xc8dxs81tpPc', address);

final ma = MixAddress.parse('MIX3QEeg1WkLrjvjxyMQf6Xc8dxs81tpPc');
Expand All @@ -23,7 +23,7 @@ void main() {
'c6d0c728-2624-429b-8e0d-d9d19b6592fa',
'67a87828-18f5-46a1-b6cc-c72a97a77c43',
];
final address = MixAddress(members: members, threshold: 4).toAddress();
final address = MixAddress.fromUuid(members, 4).toAddress();
expect(
'MIX4fwusRK88p5GexHWddUQuYJbKMJTAuBvhudgahRXKndvaM8FdPHS2Hgeo7DQxNVoSkKSEDyZeD8TYBhiwiea9PvCzay1A9Vx1C2nugc4iAmhwLGGv4h3GnABeCXHTwWEto9wEe1MWB49jLzy3nuoM81tqE2XnLvUWv',
address,
Expand All @@ -40,7 +40,7 @@ void main() {
final members = [
'XIN3BMNy9pQyj5XWDJtTbaBVE2zQ66zBo2weyc43iL286asdqwApWswAzQC5qba26fh3fzHK9iMoxyx1q3Lgj45KJftzGD9q'
];
final address = MixAddress(members: members, threshold: 1).toAddress();
final address = MixAddress.fromMainNetMixAddress(members, 1).toAddress();
expect(
'MIXPYWwhjxKsbFRzAP2Dcb2mMjj7sQQo4MpCSv3NYaYCdQ2kEcbcimpPT81gaxtuNhunLWPx7Sv7fawjZ8DhRmEj8E2hrQM4Z6e',
address);
Expand All @@ -57,7 +57,7 @@ void main() {
'XINMd9kCbxEoEetZuDM8gGJS11X3TVrRLwzhnqgMr65qjJBkCncNqSAngESpC7Hddnsw1D9Jo2QJakbFPr8WyrM6VkskGkB8',
'XINLM7VuMYSjvKiEQPyLpaG7NDLDPngWWFBZpVJjhGamMsgPbmeSsGs3fQzNoqSr6syBTyLM3i69T7iSN8Tru7aQadiKLkSV',
];
final address = MixAddress(members: members, threshold: 2).toAddress();
final address = MixAddress.fromMainNetMixAddress(members, 2).toAddress();
expect(
'MIXBCirWksVv9nuphqbtNRZZvwKsXHHMUnB5hVrVY1P7f4eBdLpDoLwiQoHYPvXia2wFepnX6hJwTjHybzBiroWVEMaFHeRFfLpcU244tzRM8smak9iRAD4PJRHN1MLHRWFtErottp9t7piaRVZBzsQXpSsaSgagj93voQdUuXhuQGZNj3Fme5YYMHfJBWjoRFHis4mnhBgxkyEGRUHAVYnfej2FhrypJmMDu74irRTdj2xjQYr6ovBJSUBYDBcvAyLPE3cEKc4JsPz7b9',
address,
Expand Down

0 comments on commit d343b76

Please sign in to comment.