From 30bafa0185b6db212fcd92908d1ee111afbb065d Mon Sep 17 00:00:00 2001
From: Hugo Trentesaux <hugo@trentesaux.fr>
Date: Tue, 22 Nov 2022 09:56:21 +0100
Subject: [PATCH] feat: batch transfer

---
 README.md                |   4 ++++
 res/metadata.scale       | Bin 128198 -> 127211 bytes
 src/commands/transfer.rs |  33 +++++++++++++++++++++++++++++++++
 src/main.rs              |  30 +++++++++++++++++++++++++-----
 4 files changed, 62 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index 640f0fd..a23d0ff 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,10 @@
 
 CLI client for [Duniter-V2S](https://git.duniter.org/nodes/rust/duniter-v2s/).
 
+Using
+- https://github.com/duniter/substrate
+- https://github.com/duniter/subxt
+
 ## Usage
 
 If using a different runtime, update the metadata for the client to compile:
diff --git a/res/metadata.scale b/res/metadata.scale
index 3ec2c111f3089edbf7b193d08cddc0ea353bec76..d7c41820b07a0a2e6198009041d239e70602a2d9 100644
GIT binary patch
delta 2014
zcmc&#X-rgC6u#e^%K*wUxQ@6DwN_9JYN4V<D*`RVA{ubP4PR#-@RXT>HxG0Y7f@VN
zeh{jsm{{e9CYox4Et%SCE2;KJ6C3{+SKKvfW2M%lX<D0}H$$Vf{o6EoFZbMY?pg15
z&i(d5^ba?q?Ox4P6V5c*yX?rQpbZ`jp~vqbqd%l@!r@U(6_n=2+lZoCH`D>~5~)QV
zGgxY>W_>^i$hL~>!{~Q6=1z%{(pge#>VBV;Wr&rCam@AmG$Dke)bG`#h?T4$!i$U%
z4iQi5JSZKM#B61-6#NMp<A|4Hm~8uGN-{n3Aa;C>nP+IK={M&3&D@$A{~FCx<`1(p
zD-c%M-*<q%(l9hd_h>#-H+3yA&ywfXu(l_d=l50WhF9|l&?pU)piqgm*r-uMCM0!f
zDBvW2vzT^DuS5o-X;~%jVyLAOuOpVaD=`NB=x!y(p+Ch}VItz_%_^3~lfMeHkwC|)
zFd2z-rwVCwq!y!X4*4OStALZP)gpSf>hTy_Ah7IjsYD_R=aMAXvB;%H(sY@AmpdYL
z^<@>LIu%X~poBWOW~_YC&O)_D=lhBjWuR<)$xuU|__?L{YK+1lO0Px^29s_rL+D^N
zreP@EsKy%{^iUmBF^o!dyozKpbu?on{i<V*k|Nu>dy;A8DkLD4R#f2rfSe*<*hw)%
zDxZNPY}B-hD<<hn15>!RKN@V?PN@OjMbpv%mx^l}Kt9*@#A>{+jFoLSbB55$8W>8N
zMUSS9CgR}S?Zwo*>^#jdpJ&X_tF<_KrS4WO(7Am4tM!Z&J@CyTEx#xbsJF1<FIIg)
z>!eU<ju(sh&L`C)jpO>>i|Mu~`IP2*kwWP{>{KTH7kN$oUvlZ6749b?#WdMA>(=m4
zbpvh#Gw9v5I2xC03FFQx!YK+u_8<<+*dnVBi=`;0#0?lh=hyRf&9~@taSOtx%B543
zOPaI<8H1NgS;`iKD&btHxpT^93bC9rHllTqYS~#X1$R96v(8F-pOUvvsqO_OFF6~r
zA-2v^@=8%3+SiC(3WAii8DC&M^=`%vr72>74mV?fvPHJ}%SX}GW;n2e9yMdU(jsZu
z1O{w_frQXoTd)aerH<{4>kC^^lCn#3q^UfJm=My{F89g~c|abLo${pYmZF`K_AtQJ
zZQSmABlO4HFaaGA`pGtoqt<O0PMO>B3SF?My6t$a-w}zp0z(zzNQj+vI+RY?ma*GO
zHSHK~@3Nd8r<QiO&_zd?h2s%>ciNGTlNLQ)>6X-*j}ak#4>FUL#Y$-3XZU=KGAo<P
zFCyC}TE>MwxX8l@Q|R<1q;eZQx&)U!ijxt724BWl<(Z9gF5?)I=<#J%Ikf{c{tEVo
ziL+Nw!W}&FD#ml`7GA|^n@BF9#2$ETjbx{W9;{&OpYP$BHkO|C;JAHOHrHqvox8#q
zxOj~*;Gu-uD71;pk7)UIjy#QaUT3!)V&`>Mvqd5b2P6nLkQMQ?iH_eu-oLn;NM~;H
z&<wjfNc(PK;6HsPzT|T<ZN1H|vqGQVMxKIc^vhk0wPnc}H2NMp%ndELhouS%sPjJ7
zpor2Q@Ng@l!UwoKdcK@FXrAF;8$@J@8DZ`CfdFKbSU|ZCaROTCkB8`%sHanpQO@v7
z?nNcTuc?<Y(L~33u?Sm2{eHsNz!v)M2^L~UDCK8FVR(zoZI#khm{+a2SM!UvL^J%Q
zn$M#d?Uefz1C71XksTJ6tzD=Y0iIPI(lIH_c}MeU0X=X)x*{iv{f5bt+6*3&uGGH_
ty0uQ(m>%9O()6lo^Q3HCnOChERa%*$`U0xknt$`upqHP%ZrRu+{{(`pY`6db

delta 2671
zcmeHJZERF!7Ji>Q@06hwoKi{(6ugKa)smJkwWgqBTck1Nqb*n@Sa0urr@b<BZ<)CR
z6hCOCMp5`toAv;S$?`+8=$33XFtS;qBw|n@F}nywz62Ln7yS6K{&Uwo_YM%V@t^UZ
z$t3Tc^PclO&pG#f&$-twkNV*2QF%$_8RJg&<Q>dIMQhR0P9u>}UaOUHY)_?HH@O~d
zSdU_q)3^?NHDhzV<#pAhl;PQ~vqGh8*IMR!jVj%#Oix)NBzMtCg-Kf-*V9us%`dS}
zo#STOI~tX#?2XE*Q7JFo>;_26#}rbYVlQ0<ZXBBP(@l1?<F*+PrghKNO`l!qwxt};
z_PSacjLmgP+hg_sv{4ymhkDc!VY!31bSIN+Qz_kz>ni6JK&F|&_p>}EjU^K9CS|dl
z4#Q~=<isqC)u%<C+_j7@Stu^M$)_8+t(Kn$Hman%F_*_ETCSjPT~VJ-XH=@jaQxhv
z*%v1w@{^ohHw{O3{Cs&UP^7mh)5xSbF;BPC!QMtAVOyN5)$9j1yQ>`2b>em^sjQ%6
zqvanNKXA2uwj2(ZDB~==<$0@6m3|D1S>k3KOXSPW8%o^N&1$FUa~GR6NLtW}C1b);
zlpx9ij4-{x28^Z?tuP7;C1(3rO`hRp(n5r3@ssG7H(JV)<(>Yykg^$LB_h#fuA_ty
z(Iz*kq!=rOfCv^D5m8Y{BTu5KsF=;#Bq{hOWF%TB#Z+2s;Lehk6+TNW!Eegys=1lD
z5cIx*N`QXw7ma4MVF5z4wheWxd~X{Y!}PZ{R3MKulmCsP`6lKgLhUAIa1yVWxC{C8
zwuw0yLzn%%0xGufFve1gg}E3<FIy<1KPgPZZS=81HKG)W^HNB4aa3VEZH?nz6w#qL
z?!yH7HjX<{OhxVJ#dO-$jz_hbvj6CX$@Eh@#ub%T*E_+xY~Z_A%k|_O`W)5*Dz`C@
zb7-|u6$;5tGHgaYU}Gw$a>8aUVfwF)T2AGJiybWLlc%s<n<M)#SKm%s)}w&gUtEv-
zCe#LRlh3f*U^rdDVFMw77Dj(OfikX8Zvqc6?7ImJO`IzWe!uF!U-gB*#VOUQe(mub
zvnc2VLW-E|f9T4TY+njr0Ckk{aG;>x-z+$(i4xI}t;paFnZMHSA*-ZVCHo5=nND&G
z=4)#tl>>Ls+ASC=SQh{qvP2k?Dj!Ehv68ZhafB^VvRawd&5MOl)bcd?CfGh2H>_k3
zw1I;*+z86t2<rM-P=;d9;JMLVeo#h=XS2tj!GMMydSWa7f){8~H{aOpIRN^5H^yr_
zWxu;-8ijihMITM<!QB|h#(J<H*iF}-hjsgFl9kQo*2xb->U-tua=#ps!}72^CO?$)
z(>9dS%iB;%5BFjUeYy?zU_Wv9iDAfJCeylJ+=1cT^PXPJ%0DPkP?zG?b1?f+FNUMq
zVcB0XSV98>n38wI=Q~P=2B1$pA|sXUmfF~w%J6_qs?x^X%4*mp03SPuavaM&HxEMB
zSW;gtO0%yHV!>o>n#dmiGv1kzm(L+`2fcTSyVRaj&_l<C$R0Vxt(gtqe2Ur72G2u}
zXg)Un6Qb(VnCZ_S^EBS#hC1;K?hkPpXyqCFA#a|KRz?>ta_&VV+}Nj5?FiRyI=Lfw
zKlD{d(7k72h5ijeubsscm`OjL<yK!t(=K8kee@ZMwA%z-`V4Eh{a2mC(jex!OPtIc
z8a~I=69kQ%;}Bog%n;^Enspuva}3YZ#q+571)7EQuM0dw0-D3O7$X12P(^S0M2oT`
zmr$c2Mx|GnVv($)+AB;^pWSi=Yc({`>3?7oR?@PoJbhMD@+w#_E&Y;5!a9n7iN7M5
zo$*f`m3Wq}UgK1H=)voV^FiBpoolk4&RoZ1*qN>P2JZqp>AP>S8hzO%-ysY+AY;2}
z`S+L@-z&Qxs_9VX`X>HzTdh)Qp4+d>=>37MdGig+ei^L{ERQHhrS0^P)N>vUZp!1Y
m6R&Gn>azn6p7o|WEW68te~&4<y~8_3AAgT2;SXi^5&1vxv|bkg

diff --git a/src/commands/transfer.rs b/src/commands/transfer.rs
index 65c3b85..82ea9e1 100644
--- a/src/commands/transfer.rs
+++ b/src/commands/transfer.rs
@@ -4,6 +4,9 @@ use anyhow::Result;
 use sp_core::{crypto::AccountId32, sr25519::Pair};
 use subxt::tx::{BaseExtrinsicParamsBuilder, PairSigner};
 
+type Call = gdev::runtime_types::gdev_runtime::Call;
+type BalancesCall = gdev::runtime_types::pallet_balances::pallet::Call;
+
 pub async fn transfer(
     pair: Pair,
     client: Client,
@@ -35,3 +38,33 @@ pub async fn transfer(
 
     Ok(())
 }
+
+pub async fn transfer_multiple(
+    pair: Pair,
+    client: Client,
+    amount: u64,
+    dests: Vec<AccountId32>,
+) -> Result<()> {
+    // build the list of transactions from the destination accounts
+    let transactions: Vec<Call> = dests
+        .into_iter()
+        .map(|dest| {
+            Call::Balances(BalancesCall::transfer_keep_alive {
+                dest: dest.into(),
+                value: amount,
+            })
+        })
+        .collect();
+
+    // wrap these calls in a batch call
+    client
+        .tx()
+        .sign_and_submit_then_watch(
+            &gdev::tx().utility().batch(transactions),
+            &PairSigner::new(pair.clone()),
+            BaseExtrinsicParamsBuilder::new(),
+        )
+        .await?;
+
+    Ok(())
+}
diff --git a/src/main.rs b/src/main.rs
index ac32f55..c8e3cc9 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -127,11 +127,22 @@ pub enum Subcommand {
         new_key: sp_core::crypto::AccountId32,
     },
     Transfer {
-        balance: u64,
+        /// Amount to transfer
+        amount: u64,
+        /// Destination address
         dest: sp_core::crypto::AccountId32,
-        #[clap(short = 'k')]
+        /// Prevent from going below account existential deposit
+        #[clap(short = 'k', long = "keep-alive")]
         keep_alive: bool,
     },
+    /// Transfer the same amount for each space-separated address.
+    /// If an address appears mutiple times, it will get multiple times the same amount
+    TransferMultiple {
+        /// Amount given to each destination address
+        amount: u64,
+        /// List of target addresses
+        dests: Vec<sp_core::crypto::AccountId32>,
+    },
     /// Rotate and set session keys
     UpdateKeys,
 }
@@ -174,7 +185,7 @@ async fn main() -> Result<()> {
         println!("Account address: {}", account_id);
     }
 
-    let client = Client::new().await.unwrap();
+    let client = Client::new().await?;
 
     if let Some(account_id) = &account_id {
         let account = client
@@ -272,19 +283,28 @@ async fn main() -> Result<()> {
             .await?
         }
         Subcommand::Transfer {
-            balance,
+            amount,
             dest,
             keep_alive,
         } => {
             commands::transfer::transfer(
                 pair.expect("This subcommand needs a secret."),
                 client,
-                balance,
+                amount,
                 dest,
                 keep_alive,
             )
             .await?
         }
+        Subcommand::TransferMultiple { amount, dests } => {
+            commands::transfer::transfer_multiple(
+                pair.expect("This subcommand needs a secret."),
+                client,
+                amount,
+                dests,
+            )
+            .await?
+        }
         Subcommand::UpdateKeys => commands::smith::update_session_keys(
             pair.expect("This subcommand needs a secret."),
             client,
-- 
GitLab