Skip to content

Commit 2c0a4a8

Browse files
committed
Transfer tokens from one account to other
1 parent 56d9112 commit 2c0a4a8

File tree

2 files changed

+207
-0
lines changed

2 files changed

+207
-0
lines changed

Accounts/worldcupticketbooking/README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,72 @@ flow start QuerybyAccount whoAmI: buyer3
124124
Confirm who owns the [FungibleToken](https://training.corda.net/libraries/tokens-sdk/#fungibletoken) (cash) and [NonFungibleToken](https://training.corda.net/libraries/tokens-sdk/#nonfungibletoken) (ticket) again by running this on Dealer1's node.
125125

126126

127+
## Transfer tokens from one account to other
128+
129+
For someone who is looking into how to only transfer tokens from one account to other use below steps.
130+
131+
132+
### Step 1
133+
```
134+
flow start CreateAndShareAccountFlow accountName: agent1, partyToShareAccountInfoToList: [BCCI, Dealer2]
135+
flow start CreateAndShareAccountFlow accountName: buyer1, partyToShareAccountInfoToList: [Bank, Dealer2]
136+
flow start CreateAndShareAccountFlow accountName: buyer2, partyToShareAccountInfoToList: [Bank, Dealer2]
137+
```
138+
Run the above flow on the Dealer1 node. This will create the agent1, buyer1 and buyer2 accounts on the Dealer1 node and share this account info with BCCI, Bank, and Dealer2 node respecticely.
139+
140+
Then let's go to the Dealer2 node and create buyer3 account:
141+
```
142+
flow start CreateAndShareAccountFlow accountName: buyer3, partyToShareAccountInfoToList: [Bank, Dealer1]
143+
```
144+
145+
Run the below query to confirm if accounts are created on Dealer1 node. Also run the above query on Bank and BCCI node to confirm if account info is shared with these nodes.
146+
147+
run vaultQuery contractStateType : com.r3.corda.lib.accounts.contracts.states.AccountInfo
148+
149+
150+
### Step 2
151+
152+
```
153+
start IssueCashFlow accountName : buyer1 , currency : USD , amount : 77
154+
155+
```
156+
Run the above command on the Bank node, which will issue 77 USD to buyer1 account.
157+
158+
### Step 3
159+
```
160+
flow start QuerybyAccount whoAmI: buyer1
161+
```
162+
You can check balance of buyer1 account at Dealer1's node
163+
[Option] You can also run the below command to confirm if 20 USD fungible tokens are stored at Dealer1's node. The current holder field in the output will be an AnonymousParty which specifies an account.
164+
```
165+
run vaultQuery contractStateType : com.r3.corda.lib.tokens.contracts.states.FungibleToken
166+
```
167+
168+
### Step 4
169+
170+
start MoveTokensBetweenAccounts buyerAccountName : buyer1, sellerAccountName : buyer3 , currency : USD , costOfTicket : 10
171+
172+
This will move tokens from account buyer1 to account buyer3
173+
174+
175+
176+
### Step 5
177+
```
178+
flow start QuerybyAccount whoAmI: buyer1
179+
```
180+
You can check balance of buyer1 account at Dealer1's node
181+
```
182+
run vaultQuery contractStateType : com.r3.corda.lib.tokens.contracts.states.FungibleToken
183+
```
184+
185+
### Step 6
186+
```
187+
flow start QuerybyAccount whoAmI: buyer3
188+
```
189+
You can check balance of buyer3 account at Dealer2's node
190+
```
191+
run vaultQuery contractStateType : com.r3.corda.lib.tokens.contracts.states.FungibleToken
192+
```
127193
## Further Reading
128194

129195
For accounts visit https://github.com/corda/accounts.
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package com.t20worldcup.flows;
2+
3+
import co.paralleluniverse.fibers.Suspendable;
4+
import com.r3.corda.lib.accounts.contracts.states.AccountInfo;
5+
import com.r3.corda.lib.accounts.workflows.UtilitiesKt;
6+
import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount;
7+
import com.r3.corda.lib.tokens.contracts.states.FungibleToken;
8+
import com.r3.corda.lib.tokens.contracts.types.TokenType;
9+
import com.r3.corda.lib.tokens.selection.TokenQueryBy;
10+
import com.r3.corda.lib.tokens.selection.database.config.DatabaseSelectionConfigKt;
11+
import com.r3.corda.lib.tokens.selection.database.selector.DatabaseTokenSelection;
12+
import com.r3.corda.lib.tokens.workflows.flows.move.MoveTokensUtilitiesKt;
13+
import com.r3.corda.lib.tokens.workflows.utilities.QueryUtilitiesKt;
14+
import kotlin.Pair;
15+
import net.corda.core.contracts.Amount;
16+
import net.corda.core.contracts.CommandData;
17+
import net.corda.core.contracts.CommandWithParties;
18+
import net.corda.core.contracts.StateAndRef;
19+
import net.corda.core.flows.*;
20+
import net.corda.core.identity.AbstractParty;
21+
import net.corda.core.identity.AnonymousParty;
22+
import net.corda.core.identity.Party;
23+
import net.corda.core.node.services.vault.QueryCriteria;
24+
import net.corda.core.transactions.SignedTransaction;
25+
import net.corda.core.transactions.TransactionBuilder;
26+
27+
import java.security.PublicKey;
28+
import java.util.*;
29+
30+
/**
31+
* This flow only talks about moving fungible tokens from one account to other.
32+
*/
33+
@StartableByRPC
34+
@InitiatingFlow
35+
public class MoveTokensBetweenAccounts extends FlowLogic<String> {
36+
37+
private final String buyerAccountName;
38+
private final String sellerAccountName;
39+
private final String currency;
40+
private final Long costOfTicket;
41+
42+
public MoveTokensBetweenAccounts(String buyerAccountName, String sellerAccountName, String currency, Long costOfTicket) {
43+
this.buyerAccountName = buyerAccountName;
44+
this.sellerAccountName = sellerAccountName;
45+
this.currency = currency;
46+
this.costOfTicket = costOfTicket;
47+
}
48+
49+
@Override
50+
@Suspendable
51+
public String call() throws FlowException {
52+
53+
//Get buyers and sellers account infos
54+
AccountInfo buyerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(buyerAccountName).get(0).getState().getData();
55+
AccountInfo sellerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(sellerAccountName).get(0).getState().getData();
56+
57+
//Generate new keys for buyers and sellers
58+
AnonymousParty buyerAccount = subFlow(new RequestKeyForAccount(buyerAccountInfo));//mappng saved locally
59+
AnonymousParty sellerAccount = subFlow(new RequestKeyForAccount(sellerAccountInfo));//mappiing requested from counterparty. does the counterparty save i dont think so
60+
61+
//buyer will create generate a move tokens state and send this state with new holder(seller) to seller
62+
Amount<TokenType> amount = new Amount(costOfTicket, getInstance(currency));
63+
64+
//Buyer Query for token balance.
65+
QueryCriteria queryCriteria = QueryUtilitiesKt.heldTokenAmountCriteria(this.getInstance(currency), buyerAccount).and(QueryUtilitiesKt.sumTokenCriteria());
66+
List<Object> sum = getServiceHub().getVaultService().queryBy(FungibleToken.class, queryCriteria).component5();
67+
if(sum.size() == 0)
68+
throw new FlowException(buyerAccountName + " has 0 token balance. Please ask the Bank to issue some cash.");
69+
else {
70+
Long tokenBalance = (Long) sum.get(0);
71+
if(tokenBalance < costOfTicket)
72+
throw new FlowException("Available token balance of " + buyerAccountName+ " is less than the cost of the ticket. Please ask the Bank to issue some cash if you wish to buy the ticket ");
73+
}
74+
75+
//the tokens to move to new account which is the seller account
76+
Pair<AbstractParty, Amount<TokenType>> partyAndAmount = new Pair(sellerAccount, amount);
77+
78+
//let's use the DatabaseTokenSelection to get the tokens from the db
79+
DatabaseTokenSelection tokenSelection = new DatabaseTokenSelection(
80+
getServiceHub(),
81+
DatabaseSelectionConfigKt.MAX_RETRIES_DEFAULT,
82+
DatabaseSelectionConfigKt.RETRY_SLEEP_DEFAULT,
83+
DatabaseSelectionConfigKt.RETRY_CAP_DEFAULT,
84+
DatabaseSelectionConfigKt.PAGE_SIZE_DEFAULT
85+
);
86+
87+
//call generateMove which gives us 2 stateandrefs with tokens having new owner as seller.
88+
Pair<List<StateAndRef<FungibleToken>>, List<FungibleToken>> inputsAndOutputs =
89+
tokenSelection.generateMove(Arrays.asList(partyAndAmount), buyerAccount, new TokenQueryBy(), getRunId().getUuid());
90+
91+
Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
92+
93+
TransactionBuilder transactionBuilder = new TransactionBuilder(notary);
94+
95+
MoveTokensUtilitiesKt.addMoveTokens(transactionBuilder, inputsAndOutputs.getFirst(), inputsAndOutputs.getSecond());
96+
97+
Set<PublicKey> mySigners = new HashSet<>();
98+
99+
List<CommandWithParties<CommandData>> commandWithPartiesList = transactionBuilder.toLedgerTransaction(getServiceHub()).getCommands();
100+
101+
for(CommandWithParties<CommandData> commandDataCommandWithParties : commandWithPartiesList) {
102+
if(((ArrayList<PublicKey>)(getServiceHub().getKeyManagementService().filterMyKeys(commandDataCommandWithParties.getSigners()))).size() > 0) {
103+
mySigners.add(((ArrayList<PublicKey>)getServiceHub().getKeyManagementService().filterMyKeys(commandDataCommandWithParties.getSigners())).get(0));
104+
}
105+
}
106+
107+
FlowSession sellerSession = initiateFlow(sellerAccountInfo.getHost());
108+
109+
//sign the transaction with the signers we got by calling filterMyKeys
110+
SignedTransaction selfSignedTransaction = getServiceHub().signInitialTransaction(transactionBuilder, mySigners);
111+
112+
//call FinalityFlow for finality
113+
subFlow(new FinalityFlow(selfSignedTransaction, Arrays.asList(sellerSession)));
114+
115+
return null;
116+
}
117+
118+
public TokenType getInstance(String currencyCode) {
119+
Currency currency = Currency.getInstance(currencyCode);
120+
return new TokenType(currency.getCurrencyCode(), 0);
121+
}
122+
}
123+
124+
@InitiatedBy(MoveTokensBetweenAccounts.class)
125+
class MoveTokensBetweenAccountsResponder extends FlowLogic<Void> {
126+
127+
private final FlowSession otherSide;
128+
129+
public MoveTokensBetweenAccountsResponder(FlowSession otherSide) {
130+
this.otherSide = otherSide;
131+
}
132+
133+
@Override
134+
@Suspendable
135+
public Void call() throws FlowException {
136+
137+
subFlow(new ReceiveFinalityFlow(otherSide));
138+
139+
return null;
140+
}
141+
}

0 commit comments

Comments
 (0)