在不同节点上的帐户之间转移可替代令牌
Transfer fungible token between accounts on different nodes
我对 Corda 很陌生。我想创建一个没有 DVP 过程的像比特币这样的资产转移环境。我将我的代码基于 worldcupticketbooking 示例项目并修改 DVPAccountsHostedOnDifferentNodes class。我计划只发送与任何票无关的令牌。我 运行 代码,创建帐户,并向他们发行资产。但是当我尝试使用此命令在帐户之间转移令牌时
flow start MoveTokensBetweenAccounts senderAccountName: buyer3,
receiverAccountName: buyer1, costOfTicket: 10, currency: USD
我收到以下错误:
✅ 开始
➡️ Receiving transaction proposal for signing.
Verifying transaction proposal.
Signing transaction proposal.
✅ 完成
☠ 逆流错误
这是我的代码,因为我无法调试它,所以我没能找到问题所在。谁能帮我看看这是怎么回事?
InitiatingFlow
@StartableByRPC
class MoveTokensBetweenAccounts(private val senderAccountName:String,
private val receiverAccountName:String,
private val costOfTicket: Long,
private val currency: String) : FlowLogic<String>(){
@Suspendable
override fun call():String {
//get sender info and account
val senderInfo = accountService.accountInfo(senderAccountName)[0].state.data
val senderAcct = subFlow(RequestKeyForAccount(senderInfo))
//get receiver info and account
val receiverInfo = accountService.accountInfo(receiverAccountName).single().state.data
val receiverAcct = subFlow(RequestKeyForAccount(receiverInfo))
//sender will create generate a move tokens state and send this state with new holder(seller) to receiver
val amount = Amount(costOfTicket, getInstance(currency))
val receiverSession = initiateFlow(receiverInfo.host)
//send uuid, buyer,seller account name to seller
receiverSession.send(senderAccountName)
receiverSession.send(receiverAccountName)
//sender Query for token balance.
val queryCriteria = heldTokenAmountCriteria(getInstance(currency), senderAcct).and(sumTokenCriteria())
val sum = serviceHub.vaultService.queryBy(FungibleToken::class.java, queryCriteria).component5()
if (sum.size == 0) throw FlowException("$senderAccountName has 0 token balance. Please ask the Bank to issue some cash.") else {
val tokenBalance = sum[0] as Long
if (tokenBalance < costOfTicket) throw FlowException("Available token balance of $senderAccountName is less than the cost of the ticket. Please ask the Bank to issue some cash if you wish to buy the ticket ")
}
//the tokens to move to new account which is the seller account
val partyAndAmount:Pair<AbstractParty, Amount<TokenType>> = Pair(receiverAcct, amount)
//let's use the DatabaseTokenSelection to get the tokens from the db
val tokenSelection = DatabaseTokenSelection(serviceHub, MAX_RETRIES_DEFAULT,
RETRY_SLEEP_DEFAULT, RETRY_CAP_DEFAULT, PAGE_SIZE_DEFAULT)
//call generateMove which gives us 2 stateandrefs with tokens having new owner as seller.
val inputsAndOutputs = tokenSelection
.generateMove(Arrays.asList(partyAndAmount), senderAcct, TokenQueryBy(), runId.uuid)
//send the generated inputsAndOutputs to the seller
subFlow(SendStateAndRefFlow(receiverSession, inputsAndOutputs.first))
receiverSession.send(inputsAndOutputs.second)
//sync following keys with seller - buyeraccounts, selleraccounts which we generated above using RequestKeyForAccount, and IMP: also share the anonymouse keys
//created by the above token move method for the holder.
val signers: MutableList<AbstractParty> = ArrayList()
signers.add(senderAcct)
signers.add(receiverAcct)
val inputs = inputsAndOutputs.first
for ((state) in inputs) {
signers.add(state.data.holder)
}
//Sync our associated keys with the conterparties.
subFlow(SyncKeyMappingFlow(receiverSession, signers))
//this is the handler for synckeymapping called by seller. seller must also have created some keys not known to us - buyer
subFlow(SyncKeyMappingFlowHandler(receiverSession))
//recieve the data from counter session in tx formatt.
subFlow(object : SignTransactionFlow(receiverSession) {
@Throws(FlowException::class)
override fun checkTransaction(stx: SignedTransaction) {
// Custom Logic to validate transaction.
}
})
val stx = subFlow(ReceiveFinalityFlow(receiverSession))
return ("The ticket is sold to $receiverAccountName"+ "\ntxID: "+stx.id)
}
}
@InitiatedBy(MoveTokensBetweenAccounts::class)
class MoveTokensBetweenAccountsResponder(val otherSide: FlowSession) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call():SignedTransaction {
//get all the details from the seller
//val tokenId: String = otherSide.receive(String::class.java).unwrap { it }
val senderAccountName: String = otherSide.receive(String::class.java).unwrap { it }
val receiverAccountName: String = otherSide.receive(String::class.java).unwrap{ it }
val inputs = subFlow(ReceiveStateAndRefFlow<FungibleToken>(otherSide))
val moneyReceived: List<FungibleToken> = otherSide.receive(List::class.java).unwrap{ it } as List<FungibleToken>
//call SyncKeyMappingHandler for SyncKey Mapping called at buyers side
subFlow(SyncKeyMappingFlowHandler(otherSide))
//Get buyers and sellers account infos
val senderAccountInfo = accountService.accountInfo(senderAccountName)[0].state.data
val receiverAccountInfo = accountService.accountInfo(receiverAccountName)[0].state.data
//Generate new keys for buyers and sellers
val senderAccount = subFlow(RequestKeyForAccount(senderAccountInfo))
val receiverAccount = subFlow(RequestKeyForAccount(receiverAccountInfo))
//building transaction
val notary = serviceHub.networkMapCache.notaryIdentities[0]
val txBuilder = TransactionBuilder(notary)
//part2 of DVP is to transfer cash - fungible token from buyer to seller and return the change to buyer
addMoveTokens(txBuilder, inputs, moneyReceived)
//add signers
val signers: MutableList<AbstractParty> = ArrayList()
signers.add(senderAccount)
signers.add(receiverAccount)
for ((state) in inputs) {
signers.add(state.data.holder)
}
//sync keys with buyer, again sync for similar members
subFlow(SyncKeyMappingFlow(otherSide, signers))
//call filterMyKeys to get the my signers for seller node and pass in as a 4th parameter to CollectSignaturesFlow.
//by doing this we tell CollectSignaturesFlow that these are the signers which have already signed the transaction
val commandWithPartiesList: List<CommandWithParties<CommandData>> = txBuilder.toLedgerTransaction(serviceHub).commands
val mySigners: MutableList<PublicKey> = ArrayList()
commandWithPartiesList.map {
val signer = (serviceHub.keyManagementService.filterMyKeys(it.signers) as ArrayList<PublicKey>)
if(signer.size >0){
mySigners.add(signer[0]) }
}
val selfSignedTransaction = serviceHub.signInitialTransaction(txBuilder, mySigners)
val fullySignedTx = subFlow(CollectSignaturesFlow(selfSignedTransaction, listOf(otherSide), mySigners))
//call FinalityFlow for finality
return subFlow(FinalityFlow(fullySignedTx, Arrays.asList(otherSide)))
}
}
我建议您查看 here 上的令牌示例。此示例简单地介绍了创建、发布和传输的步骤。
如果您的设计中没有任何dvp动作。我不建议您查看 worldcupticketbooking 示例,其中 dvp 使流程变得非常复杂。
实际上我建议您查看 tic-tac-thor 示例以熟悉帐户流程逻辑。然后尝试从 worldcupticketbooking 示例中模仿您的设计。
正如所讨论的,使用下面的代码在账户之间移动代币。您无需调用 DVP 流程。一个简单的MoveTokensUtilitiesKt.addMoveTokens应该就够了。
package com.t20worldcup.flows;
import co.paralleluniverse.fibers.Suspendable;
import com.r3.corda.lib.accounts.contracts.states.AccountInfo;
import com.r3.corda.lib.accounts.workflows.UtilitiesKt;
import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount;
import com.r3.corda.lib.tokens.contracts.states.FungibleToken;
import com.r3.corda.lib.tokens.contracts.types.TokenType;
import com.r3.corda.lib.tokens.selection.TokenQueryBy;
import com.r3.corda.lib.tokens.selection.database.config.DatabaseSelectionConfigKt;
import com.r3.corda.lib.tokens.selection.database.selector.DatabaseTokenSelection;
import com.r3.corda.lib.tokens.workflows.flows.move.MoveTokensUtilitiesKt;
import com.r3.corda.lib.tokens.workflows.utilities.QueryUtilitiesKt;
import kotlin.Pair;
import net.corda.core.contracts.Amount;
import net.corda.core.contracts.CommandData;
import net.corda.core.contracts.CommandWithParties;
import net.corda.core.contracts.StateAndRef;
import net.corda.core.flows.*;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.AnonymousParty;
import net.corda.core.identity.Party;
import net.corda.core.node.services.vault.QueryCriteria;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.transactions.TransactionBuilder;
import java.security.PublicKey;
import java.util.*;
@StartableByRPC
@InitiatingFlow
public class MoveTokensBetweenAccounts extends FlowLogic<String> {
private final String buyerAccountName;
private final String sellerAccountName;
private final String currency;
private final Long costOfTicket;
public MoveTokensBetweenAccounts(String buyerAccountName, String sellerAccountName, String currency, Long costOfTicket) {
this.buyerAccountName = buyerAccountName;
this.sellerAccountName = sellerAccountName;
this.currency = currency;
this.costOfTicket = costOfTicket;
}
@Override
@Suspendable
public String call() throws FlowException {
//Get buyers and sellers account infos
AccountInfo buyerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(buyerAccountName).get(0).getState().getData();
AccountInfo sellerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(sellerAccountName).get(0).getState().getData();
//Generate new keys for buyers and sellers
//make sure to sync these keys with the counterparty by calling SyncKeyMappingFlow as below
AnonymousParty buyerAccount = subFlow(new RequestKeyForAccount(buyerAccountInfo));//mappng saved locally
AnonymousParty sellerAccount = subFlow(new RequestKeyForAccount(sellerAccountInfo));//mappiing requested from counterparty. does the counterparty save i dont think so
//buyer will create generate a move tokens state and send this state with new holder(seller) to seller
Amount<TokenType> amount = new Amount(costOfTicket, getInstance(currency));
//Buyer Query for token balance.
QueryCriteria queryCriteria = QueryUtilitiesKt.heldTokenAmountCriteria(this.getInstance(currency), buyerAccount).and(QueryUtilitiesKt.sumTokenCriteria());
List<Object> sum = getServiceHub().getVaultService().queryBy(FungibleToken.class, queryCriteria).component5();
if(sum.size() == 0)
throw new FlowException(buyerAccountName + " has 0 token balance. Please ask the Bank to issue some cash.");
else {
Long tokenBalance = (Long) sum.get(0);
if(tokenBalance < costOfTicket)
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 ");
}
//the tokens to move to new account which is the seller account
Pair<AbstractParty, Amount<TokenType>> partyAndAmount = new Pair(sellerAccount, amount);
//let's use the DatabaseTokenSelection to get the tokens from the db
DatabaseTokenSelection tokenSelection = new DatabaseTokenSelection(
getServiceHub(),
DatabaseSelectionConfigKt.MAX_RETRIES_DEFAULT,
DatabaseSelectionConfigKt.RETRY_SLEEP_DEFAULT,
DatabaseSelectionConfigKt.RETRY_CAP_DEFAULT,
DatabaseSelectionConfigKt.PAGE_SIZE_DEFAULT
);
//call generateMove which gives us 2 stateandrefs with tokens having new owner as seller.
Pair<List<StateAndRef<FungibleToken>>, List<FungibleToken>> inputsAndOutputs =
tokenSelection.generateMove(Arrays.asList(partyAndAmount), buyerAccount, new TokenQueryBy(), getRunId().getUuid());
Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
TransactionBuilder transactionBuilder = new TransactionBuilder(notary);
MoveTokensUtilitiesKt.addMoveTokens(transactionBuilder, inputsAndOutputs.getFirst(), inputsAndOutputs.getSecond());
Set<PublicKey> mySigners = new HashSet<>();
List<CommandWithParties<CommandData>> commandWithPartiesList = transactionBuilder.toLedgerTransaction(getServiceHub()).getCommands();
for(CommandWithParties<CommandData> commandDataCommandWithParties : commandWithPartiesList) {
if(((ArrayList<PublicKey>)(getServiceHub().getKeyManagementService().filterMyKeys(commandDataCommandWithParties.getSigners()))).size() > 0) {
mySigners.add(((ArrayList<PublicKey>)getServiceHub().getKeyManagementService().filterMyKeys(commandDataCommandWithParties.getSigners())).get(0));
}
}
FlowSession sellerSession = initiateFlow(sellerAccountInfo.getHost());
//sign the transaction with the signers we got by calling filterMyKeys
SignedTransaction selfSignedTransaction = getServiceHub().signInitialTransaction(transactionBuilder, mySigners);
//call FinalityFlow for finality
subFlow(new FinalityFlow(selfSignedTransaction, Arrays.asList(sellerSession)));
return null;
}
public TokenType getInstance(String currencyCode) {
Currency currency = Currency.getInstance(currencyCode);
return new TokenType(currency.getCurrencyCode(), 0);
}
}
@InitiatedBy(MoveTokensBetweenAccounts.class)
class MoveTokensBetweenAccountsResponder extends FlowLogic<Void> {
private final FlowSession otherSide;
public MoveTokensBetweenAccountsResponder(FlowSession otherSide) {
this.otherSide = otherSide;
}
@Override
@Suspendable
public Void call() throws FlowException {
subFlow(new ReceiveFinalityFlow(otherSide));
return null;
}
}
我对 Corda 很陌生。我想创建一个没有 DVP 过程的像比特币这样的资产转移环境。我将我的代码基于 worldcupticketbooking 示例项目并修改 DVPAccountsHostedOnDifferentNodes class。我计划只发送与任何票无关的令牌。我 运行 代码,创建帐户,并向他们发行资产。但是当我尝试使用此命令在帐户之间转移令牌时
flow start MoveTokensBetweenAccounts senderAccountName: buyer3, receiverAccountName: buyer1, costOfTicket: 10, currency: USD
我收到以下错误:
✅ 开始
➡️ Receiving transaction proposal for signing.
Verifying transaction proposal.
Signing transaction proposal.
✅ 完成
☠ 逆流错误
这是我的代码,因为我无法调试它,所以我没能找到问题所在。谁能帮我看看这是怎么回事?
InitiatingFlow
@StartableByRPC
class MoveTokensBetweenAccounts(private val senderAccountName:String,
private val receiverAccountName:String,
private val costOfTicket: Long,
private val currency: String) : FlowLogic<String>(){
@Suspendable
override fun call():String {
//get sender info and account
val senderInfo = accountService.accountInfo(senderAccountName)[0].state.data
val senderAcct = subFlow(RequestKeyForAccount(senderInfo))
//get receiver info and account
val receiverInfo = accountService.accountInfo(receiverAccountName).single().state.data
val receiverAcct = subFlow(RequestKeyForAccount(receiverInfo))
//sender will create generate a move tokens state and send this state with new holder(seller) to receiver
val amount = Amount(costOfTicket, getInstance(currency))
val receiverSession = initiateFlow(receiverInfo.host)
//send uuid, buyer,seller account name to seller
receiverSession.send(senderAccountName)
receiverSession.send(receiverAccountName)
//sender Query for token balance.
val queryCriteria = heldTokenAmountCriteria(getInstance(currency), senderAcct).and(sumTokenCriteria())
val sum = serviceHub.vaultService.queryBy(FungibleToken::class.java, queryCriteria).component5()
if (sum.size == 0) throw FlowException("$senderAccountName has 0 token balance. Please ask the Bank to issue some cash.") else {
val tokenBalance = sum[0] as Long
if (tokenBalance < costOfTicket) throw FlowException("Available token balance of $senderAccountName is less than the cost of the ticket. Please ask the Bank to issue some cash if you wish to buy the ticket ")
}
//the tokens to move to new account which is the seller account
val partyAndAmount:Pair<AbstractParty, Amount<TokenType>> = Pair(receiverAcct, amount)
//let's use the DatabaseTokenSelection to get the tokens from the db
val tokenSelection = DatabaseTokenSelection(serviceHub, MAX_RETRIES_DEFAULT,
RETRY_SLEEP_DEFAULT, RETRY_CAP_DEFAULT, PAGE_SIZE_DEFAULT)
//call generateMove which gives us 2 stateandrefs with tokens having new owner as seller.
val inputsAndOutputs = tokenSelection
.generateMove(Arrays.asList(partyAndAmount), senderAcct, TokenQueryBy(), runId.uuid)
//send the generated inputsAndOutputs to the seller
subFlow(SendStateAndRefFlow(receiverSession, inputsAndOutputs.first))
receiverSession.send(inputsAndOutputs.second)
//sync following keys with seller - buyeraccounts, selleraccounts which we generated above using RequestKeyForAccount, and IMP: also share the anonymouse keys
//created by the above token move method for the holder.
val signers: MutableList<AbstractParty> = ArrayList()
signers.add(senderAcct)
signers.add(receiverAcct)
val inputs = inputsAndOutputs.first
for ((state) in inputs) {
signers.add(state.data.holder)
}
//Sync our associated keys with the conterparties.
subFlow(SyncKeyMappingFlow(receiverSession, signers))
//this is the handler for synckeymapping called by seller. seller must also have created some keys not known to us - buyer
subFlow(SyncKeyMappingFlowHandler(receiverSession))
//recieve the data from counter session in tx formatt.
subFlow(object : SignTransactionFlow(receiverSession) {
@Throws(FlowException::class)
override fun checkTransaction(stx: SignedTransaction) {
// Custom Logic to validate transaction.
}
})
val stx = subFlow(ReceiveFinalityFlow(receiverSession))
return ("The ticket is sold to $receiverAccountName"+ "\ntxID: "+stx.id)
}
}
@InitiatedBy(MoveTokensBetweenAccounts::class)
class MoveTokensBetweenAccountsResponder(val otherSide: FlowSession) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call():SignedTransaction {
//get all the details from the seller
//val tokenId: String = otherSide.receive(String::class.java).unwrap { it }
val senderAccountName: String = otherSide.receive(String::class.java).unwrap { it }
val receiverAccountName: String = otherSide.receive(String::class.java).unwrap{ it }
val inputs = subFlow(ReceiveStateAndRefFlow<FungibleToken>(otherSide))
val moneyReceived: List<FungibleToken> = otherSide.receive(List::class.java).unwrap{ it } as List<FungibleToken>
//call SyncKeyMappingHandler for SyncKey Mapping called at buyers side
subFlow(SyncKeyMappingFlowHandler(otherSide))
//Get buyers and sellers account infos
val senderAccountInfo = accountService.accountInfo(senderAccountName)[0].state.data
val receiverAccountInfo = accountService.accountInfo(receiverAccountName)[0].state.data
//Generate new keys for buyers and sellers
val senderAccount = subFlow(RequestKeyForAccount(senderAccountInfo))
val receiverAccount = subFlow(RequestKeyForAccount(receiverAccountInfo))
//building transaction
val notary = serviceHub.networkMapCache.notaryIdentities[0]
val txBuilder = TransactionBuilder(notary)
//part2 of DVP is to transfer cash - fungible token from buyer to seller and return the change to buyer
addMoveTokens(txBuilder, inputs, moneyReceived)
//add signers
val signers: MutableList<AbstractParty> = ArrayList()
signers.add(senderAccount)
signers.add(receiverAccount)
for ((state) in inputs) {
signers.add(state.data.holder)
}
//sync keys with buyer, again sync for similar members
subFlow(SyncKeyMappingFlow(otherSide, signers))
//call filterMyKeys to get the my signers for seller node and pass in as a 4th parameter to CollectSignaturesFlow.
//by doing this we tell CollectSignaturesFlow that these are the signers which have already signed the transaction
val commandWithPartiesList: List<CommandWithParties<CommandData>> = txBuilder.toLedgerTransaction(serviceHub).commands
val mySigners: MutableList<PublicKey> = ArrayList()
commandWithPartiesList.map {
val signer = (serviceHub.keyManagementService.filterMyKeys(it.signers) as ArrayList<PublicKey>)
if(signer.size >0){
mySigners.add(signer[0]) }
}
val selfSignedTransaction = serviceHub.signInitialTransaction(txBuilder, mySigners)
val fullySignedTx = subFlow(CollectSignaturesFlow(selfSignedTransaction, listOf(otherSide), mySigners))
//call FinalityFlow for finality
return subFlow(FinalityFlow(fullySignedTx, Arrays.asList(otherSide)))
}
}
我建议您查看 here 上的令牌示例。此示例简单地介绍了创建、发布和传输的步骤。
如果您的设计中没有任何dvp动作。我不建议您查看 worldcupticketbooking 示例,其中 dvp 使流程变得非常复杂。
实际上我建议您查看 tic-tac-thor 示例以熟悉帐户流程逻辑。然后尝试从 worldcupticketbooking 示例中模仿您的设计。
正如所讨论的,使用下面的代码在账户之间移动代币。您无需调用 DVP 流程。一个简单的MoveTokensUtilitiesKt.addMoveTokens应该就够了。
package com.t20worldcup.flows;
import co.paralleluniverse.fibers.Suspendable;
import com.r3.corda.lib.accounts.contracts.states.AccountInfo;
import com.r3.corda.lib.accounts.workflows.UtilitiesKt;
import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount;
import com.r3.corda.lib.tokens.contracts.states.FungibleToken;
import com.r3.corda.lib.tokens.contracts.types.TokenType;
import com.r3.corda.lib.tokens.selection.TokenQueryBy;
import com.r3.corda.lib.tokens.selection.database.config.DatabaseSelectionConfigKt;
import com.r3.corda.lib.tokens.selection.database.selector.DatabaseTokenSelection;
import com.r3.corda.lib.tokens.workflows.flows.move.MoveTokensUtilitiesKt;
import com.r3.corda.lib.tokens.workflows.utilities.QueryUtilitiesKt;
import kotlin.Pair;
import net.corda.core.contracts.Amount;
import net.corda.core.contracts.CommandData;
import net.corda.core.contracts.CommandWithParties;
import net.corda.core.contracts.StateAndRef;
import net.corda.core.flows.*;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.AnonymousParty;
import net.corda.core.identity.Party;
import net.corda.core.node.services.vault.QueryCriteria;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.transactions.TransactionBuilder;
import java.security.PublicKey;
import java.util.*;
@StartableByRPC
@InitiatingFlow
public class MoveTokensBetweenAccounts extends FlowLogic<String> {
private final String buyerAccountName;
private final String sellerAccountName;
private final String currency;
private final Long costOfTicket;
public MoveTokensBetweenAccounts(String buyerAccountName, String sellerAccountName, String currency, Long costOfTicket) {
this.buyerAccountName = buyerAccountName;
this.sellerAccountName = sellerAccountName;
this.currency = currency;
this.costOfTicket = costOfTicket;
}
@Override
@Suspendable
public String call() throws FlowException {
//Get buyers and sellers account infos
AccountInfo buyerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(buyerAccountName).get(0).getState().getData();
AccountInfo sellerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(sellerAccountName).get(0).getState().getData();
//Generate new keys for buyers and sellers
//make sure to sync these keys with the counterparty by calling SyncKeyMappingFlow as below
AnonymousParty buyerAccount = subFlow(new RequestKeyForAccount(buyerAccountInfo));//mappng saved locally
AnonymousParty sellerAccount = subFlow(new RequestKeyForAccount(sellerAccountInfo));//mappiing requested from counterparty. does the counterparty save i dont think so
//buyer will create generate a move tokens state and send this state with new holder(seller) to seller
Amount<TokenType> amount = new Amount(costOfTicket, getInstance(currency));
//Buyer Query for token balance.
QueryCriteria queryCriteria = QueryUtilitiesKt.heldTokenAmountCriteria(this.getInstance(currency), buyerAccount).and(QueryUtilitiesKt.sumTokenCriteria());
List<Object> sum = getServiceHub().getVaultService().queryBy(FungibleToken.class, queryCriteria).component5();
if(sum.size() == 0)
throw new FlowException(buyerAccountName + " has 0 token balance. Please ask the Bank to issue some cash.");
else {
Long tokenBalance = (Long) sum.get(0);
if(tokenBalance < costOfTicket)
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 ");
}
//the tokens to move to new account which is the seller account
Pair<AbstractParty, Amount<TokenType>> partyAndAmount = new Pair(sellerAccount, amount);
//let's use the DatabaseTokenSelection to get the tokens from the db
DatabaseTokenSelection tokenSelection = new DatabaseTokenSelection(
getServiceHub(),
DatabaseSelectionConfigKt.MAX_RETRIES_DEFAULT,
DatabaseSelectionConfigKt.RETRY_SLEEP_DEFAULT,
DatabaseSelectionConfigKt.RETRY_CAP_DEFAULT,
DatabaseSelectionConfigKt.PAGE_SIZE_DEFAULT
);
//call generateMove which gives us 2 stateandrefs with tokens having new owner as seller.
Pair<List<StateAndRef<FungibleToken>>, List<FungibleToken>> inputsAndOutputs =
tokenSelection.generateMove(Arrays.asList(partyAndAmount), buyerAccount, new TokenQueryBy(), getRunId().getUuid());
Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
TransactionBuilder transactionBuilder = new TransactionBuilder(notary);
MoveTokensUtilitiesKt.addMoveTokens(transactionBuilder, inputsAndOutputs.getFirst(), inputsAndOutputs.getSecond());
Set<PublicKey> mySigners = new HashSet<>();
List<CommandWithParties<CommandData>> commandWithPartiesList = transactionBuilder.toLedgerTransaction(getServiceHub()).getCommands();
for(CommandWithParties<CommandData> commandDataCommandWithParties : commandWithPartiesList) {
if(((ArrayList<PublicKey>)(getServiceHub().getKeyManagementService().filterMyKeys(commandDataCommandWithParties.getSigners()))).size() > 0) {
mySigners.add(((ArrayList<PublicKey>)getServiceHub().getKeyManagementService().filterMyKeys(commandDataCommandWithParties.getSigners())).get(0));
}
}
FlowSession sellerSession = initiateFlow(sellerAccountInfo.getHost());
//sign the transaction with the signers we got by calling filterMyKeys
SignedTransaction selfSignedTransaction = getServiceHub().signInitialTransaction(transactionBuilder, mySigners);
//call FinalityFlow for finality
subFlow(new FinalityFlow(selfSignedTransaction, Arrays.asList(sellerSession)));
return null;
}
public TokenType getInstance(String currencyCode) {
Currency currency = Currency.getInstance(currencyCode);
return new TokenType(currency.getCurrencyCode(), 0);
}
}
@InitiatedBy(MoveTokensBetweenAccounts.class)
class MoveTokensBetweenAccountsResponder extends FlowLogic<Void> {
private final FlowSession otherSide;
public MoveTokensBetweenAccountsResponder(FlowSession otherSide) {
this.otherSide = otherSide;
}
@Override
@Suspendable
public Void call() throws FlowException {
subFlow(new ReceiveFinalityFlow(otherSide));
return null;
}
}