From cac151272c28c00eaea7f4d0d8f98808a92c79b2 Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Wed, 3 Sep 2025 09:52:28 +0100 Subject: [PATCH 01/27] Bridging using tokens move to self without any change and without confidential identities. Bridge* flows follows the structure of similar Move flows from tokens SDK. --- Tokens/constants.properties | 3 +- Tokens/stockpaydividend/build.gradle | 3 +- .../stockpaydividend/contracts/build.gradle | 4 + .../bridging/contracts/BridgingContract.kt | 70 ++++++++ .../bridging/states/BridgedAssetLockState.kt | 9 + .../stockpaydividend/workflows/build.gradle | 3 + .../bridging/BridgeFungibleTokensFlow.kt | 51 ++++++ .../tokens/bridging/BridgeTokensUtilities.kt | 84 ++++++++++ .../lib/tokens/bridging/rpc/BridgeTokens.kt | 154 ++++++++++++++++++ .../samples/stockpaydividend/FlowTests.kt | 76 ++++++++- 10 files changed, 452 insertions(+), 5 deletions(-) create mode 100644 Tokens/stockpaydividend/contracts/src/main/kotlin/com/r3/corda/lib/tokens/bridging/contracts/BridgingContract.kt create mode 100644 Tokens/stockpaydividend/contracts/src/main/kotlin/com/r3/corda/lib/tokens/bridging/states/BridgedAssetLockState.kt create mode 100644 Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeFungibleTokensFlow.kt create mode 100644 Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeTokensUtilities.kt create mode 100644 Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt diff --git a/Tokens/constants.properties b/Tokens/constants.properties index 6a0b9773..4b7e6378 100644 --- a/Tokens/constants.properties +++ b/Tokens/constants.properties @@ -9,4 +9,5 @@ quasarVersion=0.9.0_r3 log4jVersion =2.23.1 platformVersion=140 slf4jVersion=2.0.12 -nettyVersion=4.1.77.Final \ No newline at end of file +nettyVersion=4.1.77.Final +solana4jVersion=1.2.0 \ No newline at end of file diff --git a/Tokens/stockpaydividend/build.gradle b/Tokens/stockpaydividend/build.gradle index 48434afc..0edb0add 100644 --- a/Tokens/stockpaydividend/build.gradle +++ b/Tokens/stockpaydividend/build.gradle @@ -17,10 +17,11 @@ buildscript { log4j_version = constants.getProperty("log4jVersion") slf4j_version = constants.getProperty("slf4jVersion") corda_platform_version = constants.getProperty("platformVersion").toInteger() + solana4j_version = constants.getProperty("solana4jVersion") //token tokens_release_group = 'com.r3.corda.lib.tokens' - tokens_release_version = '1.3-SNAPSHOT' + tokens_release_version = '1.3' testJvmArgs = ['--add-opens', 'java.base/java.time=ALL-UNNAMED', '--add-opens', 'java.base/java.io=ALL-UNNAMED', diff --git a/Tokens/stockpaydividend/contracts/build.gradle b/Tokens/stockpaydividend/contracts/build.gradle index ee59dbad..0c2cc862 100644 --- a/Tokens/stockpaydividend/contracts/build.gradle +++ b/Tokens/stockpaydividend/contracts/build.gradle @@ -26,6 +26,10 @@ dependencies { // Token SDK dependencies. cordaProvided "$tokens_release_group:tokens-contracts:$tokens_release_version" + + //App dependencies + cordaProvided "com.lmax:solana4j:${solana4j_version}" + cordaProvided "$corda_release_group:corda-solana-sdk:$corda_release_version" } test { diff --git a/Tokens/stockpaydividend/contracts/src/main/kotlin/com/r3/corda/lib/tokens/bridging/contracts/BridgingContract.kt b/Tokens/stockpaydividend/contracts/src/main/kotlin/com/r3/corda/lib/tokens/bridging/contracts/BridgingContract.kt new file mode 100644 index 00000000..fc9190dd --- /dev/null +++ b/Tokens/stockpaydividend/contracts/src/main/kotlin/com/r3/corda/lib/tokens/bridging/contracts/BridgingContract.kt @@ -0,0 +1,70 @@ +package com.r3.corda.lib.tokens.bridging.contracts + +import com.lmax.solana4j.programs.TokenProgramBase +import com.r3.corda.lib.tokens.bridging.states.BridgedAssetLockState +import com.r3.corda.lib.tokens.contracts.commands.MoveTokenCommand +import com.r3.corda.lib.tokens.contracts.states.FungibleToken +import net.corda.core.contracts.CommandData +import net.corda.core.contracts.Contract +import net.corda.core.identity.Party +import net.corda.core.transactions.LedgerTransaction +import net.corda.solana.sdk.instruction.Pubkey +import net.corda.solana.sdk.instruction.SolanaInstruction +import net.corda.solana.sdk.internal.Token2022 +import java.nio.ByteBuffer +import java.nio.ByteOrder + +class BridgingContract : Contract { + override fun verify(tx: LedgerTransaction) { + val bridgingCommands = tx.commandsOfType() + + require(bridgingCommands.size == 1) { "Bridging transactions must have single bridging command" } + + when (val bridgingCommand = bridgingCommands.single().value) { + is BridgingCommand.BridgeToSolana -> verifyBridging(tx, bridgingCommand) + } + } + + private fun verifyBridging(tx: LedgerTransaction, bridgingCommand: BridgingCommand.BridgeToSolana) { + val lockState = tx.outputsOfType().singleOrNull() + + require(lockState != null) { "Bridging transaction must have exactly one BridgedAssetLockstate as output" } + + require(lockState.participants.single() == bridgingCommand.bridgeAuthority) { "BridgedAssetLockstate must be owned by bridging authority" } + + val moveCommands = tx.commandsOfType() + + require(moveCommands.size == 1) { "Bridging must have one move command to lock token" } + + val lockedSum = tx.outputsOfType() + .filter { it.holder == bridgingCommand.bridgeAuthority } //TODO this is mute point for now, change to != bridgeAuthority, to filter only states owned by CI .. + .sumOf { + it.amount.toDecimal().toLong() + } // ... currently can't distinguish between locked and a change, both are for same holder ... + + val instruction = tx.notaryInstructions.singleOrNull() as? SolanaInstruction + + require(instruction != null) { "Exactly one Solana mint instruction required" } + + require(instruction.programId == Token2022.PROGRAM_ID) { "Solana program id must be Token2022 program" } + + require(instruction.accounts[1].pubkey == bridgingCommand.targetAddress) { "Target in instructions does not match command" } + + @Suppress("MagicNumber") + require(instruction.data.size == 9) { "Expecting 9 bytes of instruction data" } + + val instructionBytes = ByteBuffer.wrap(instruction.data.bytes).order(ByteOrder.LITTLE_ENDIAN) + + val tokenInstruction = instructionBytes.get().toInt() + + val amount = instructionBytes.getLong() + require(tokenInstruction == TokenProgramBase.MINT_TO_INSTRUCTION) { "Token instruction must be MINT_TO_INSTRUCTION" } + + require(amount == lockedSum) { "Locked amount of $lockedSum must match requested mint amount $amount." } + } + + sealed interface BridgingCommand : CommandData { + + data class BridgeToSolana(val targetAddress: Pubkey, val bridgeAuthority: Party) : BridgingCommand + } +} \ No newline at end of file diff --git a/Tokens/stockpaydividend/contracts/src/main/kotlin/com/r3/corda/lib/tokens/bridging/states/BridgedAssetLockState.kt b/Tokens/stockpaydividend/contracts/src/main/kotlin/com/r3/corda/lib/tokens/bridging/states/BridgedAssetLockState.kt new file mode 100644 index 00000000..e65389f0 --- /dev/null +++ b/Tokens/stockpaydividend/contracts/src/main/kotlin/com/r3/corda/lib/tokens/bridging/states/BridgedAssetLockState.kt @@ -0,0 +1,9 @@ +package com.r3.corda.lib.tokens.bridging.states + +import com.r3.corda.lib.tokens.bridging.contracts.BridgingContract +import net.corda.core.contracts.BelongsToContract +import net.corda.core.contracts.ContractState +import net.corda.core.identity.AbstractParty + +@BelongsToContract(BridgingContract::class) +class BridgedAssetLockState(override val participants: List) : ContractState \ No newline at end of file diff --git a/Tokens/stockpaydividend/workflows/build.gradle b/Tokens/stockpaydividend/workflows/build.gradle index 20ad4263..432d0ea0 100644 --- a/Tokens/stockpaydividend/workflows/build.gradle +++ b/Tokens/stockpaydividend/workflows/build.gradle @@ -57,6 +57,9 @@ dependencies { // Token SDK dependencies. cordaProvided "$tokens_release_group:tokens-contracts:$tokens_release_version" cordaProvided "$tokens_release_group:tokens-workflows:$tokens_release_version" + + cordaProvided "$corda_release_group:corda-solana-sdk:$corda_release_version" + cordaProvided "$corda_release_group:corda-solana-common:$corda_release_version" } task integrationTest(type: Test, dependsOn: []) { diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeFungibleTokensFlow.kt b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeFungibleTokensFlow.kt new file mode 100644 index 00000000..f80a046d --- /dev/null +++ b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeFungibleTokensFlow.kt @@ -0,0 +1,51 @@ +package com.r3.corda.lib.tokens.bridging + +import co.paralleluniverse.fibers.Suspendable +import com.r3.corda.lib.tokens.bridging.contracts.BridgingContract +import com.r3.corda.lib.tokens.contracts.types.TokenType +import com.r3.corda.lib.tokens.workflows.flows.move.AbstractMoveTokensFlow +import com.r3.corda.lib.tokens.workflows.flows.move.addMoveFungibleTokens +import com.r3.corda.lib.tokens.workflows.types.PartyAndAmount +import net.corda.core.contracts.ContractState +import net.corda.core.flows.FlowSession +import net.corda.core.node.services.vault.QueryCriteria +import net.corda.core.transactions.TransactionBuilder +import net.corda.solana.sdk.instruction.Pubkey + +//Follows flow moves fungible tokens and adds bridging command and output state to the transaction +class BridgeFungibleTokensFlow +@JvmOverloads +constructor( + val partyAndAmount: PartyAndAmount, + override val participantSessions: List, + override val observerSessions: List = emptyList(), + val additionalOutput: ContractState, + val additionalCommand: BridgingContract.BridgingCommand, + val destination: Pubkey, + val mint: Pubkey, + val mintAuthority: Pubkey, + val queryCriteria: QueryCriteria? = null +) : AbstractMoveTokensFlow() { + + @Suspendable + override fun addMove(transactionBuilder: TransactionBuilder) { + addMoveFungibleTokens( + transactionBuilder = transactionBuilder, + serviceHub = serviceHub, + partiesAndAmounts = listOf(partyAndAmount), //TODO change to Confidential Identity and change will be distributed to current holder (BI) + changeHolder = ourIdentity, + queryCriteria = queryCriteria + ) + val quantity = partyAndAmount.amount.toDecimal().toLong() //TODO + bridgeToken( + serviceHub, + transactionBuilder, + additionalOutput, + additionalCommand, + destination, + mint, + mintAuthority, + quantity + ) + } +} \ No newline at end of file diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeTokensUtilities.kt b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeTokensUtilities.kt new file mode 100644 index 00000000..6a01e4f8 --- /dev/null +++ b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeTokensUtilities.kt @@ -0,0 +1,84 @@ +package com.r3.corda.lib.tokens.bridging + +import co.paralleluniverse.fibers.Suspendable +import com.r3.corda.lib.tokens.bridging.contracts.BridgingContract +import com.r3.corda.lib.tokens.contracts.states.AbstractToken +import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType +import net.corda.core.contracts.ContractState +import net.corda.core.contracts.StateAndRef +import net.corda.core.node.ServiceHub +import net.corda.core.transactions.TransactionBuilder +import net.corda.solana.sdk.instruction.Pubkey +import net.corda.solana.sdk.internal.Token2022 +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.forEach + +/** + * Adds a set of bridging commands to a transaction using specific outputs. + */ +@Suspendable +fun bridgeTokens( + serviceHub: ServiceHub, + transactionBuilder: TransactionBuilder, + additionalOutput: List, + additionalCommand: BridgingContract.BridgingCommand, + destination: Pubkey, + mint: Pubkey, + mintAuthority: Pubkey, + quantity: Long +): TransactionBuilder { + transactionBuilder.inputStates() + val outputGroups: Map> = + transactionBuilder.outputStates() + .map { it.data } + .filterIsInstance() + .groupBy { it.issuedTokenType } + val inputGroups: Map>> = transactionBuilder.inputStates() + .map { serviceHub.toStateAndRef(it) }.groupBy { it.state.data.issuedTokenType } + + check(outputGroups.keys == inputGroups.keys) { + "Input and output token types must correspond to each other when moving tokensToIssue" + } + + transactionBuilder.apply { + + outputGroups.forEach { (issuedTokenType: IssuedTokenType, _: List) -> + val inputGroup = inputGroups[issuedTokenType] + ?: throw IllegalArgumentException("No corresponding inputs for the outputs issued token type: $issuedTokenType") + val keys = inputGroup.map { it.state.data.holder.owningKey } + + additionalOutput.map { + addOutputState(it) + } + + addCommand(additionalCommand, keys) + } + } + if (additionalCommand is BridgingContract.BridgingCommand.BridgeToSolana) { + val instruction = Token2022.mintTo(mint, destination, mintAuthority, quantity) + transactionBuilder.addNotaryInstruction(instruction) + } + + return transactionBuilder +} + +/** + * Adds a single bridging command to a transaction. + */ +@Suspendable +fun bridgeToken( + serviceHub: ServiceHub, + transactionBuilder: TransactionBuilder, + additionalOutput: ContractState, + additionalCommand: BridgingContract.BridgingCommand, + destination: Pubkey, + mint: Pubkey, + mintAuthority: Pubkey, + quantity: Long +): TransactionBuilder { + return bridgeTokens( + serviceHub, transactionBuilder, listOf(additionalOutput), + additionalCommand, destination, mint, mintAuthority, quantity + ) +} \ No newline at end of file diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt new file mode 100644 index 00000000..141e293d --- /dev/null +++ b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt @@ -0,0 +1,154 @@ +import co.paralleluniverse.fibers.Suspendable +import com.r3.corda.lib.tokens.contracts.types.TokenType +import com.r3.corda.lib.tokens.workflows.flows.move.MoveTokensFlowHandler +import com.r3.corda.lib.tokens.workflows.types.PartyAndAmount +import com.r3.corda.lib.tokens.workflows.utilities.sessionsForParties +import net.corda.core.contracts.Amount +import net.corda.core.contracts.ContractState +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession +import net.corda.core.flows.InitiatedBy +import net.corda.core.flows.InitiatingFlow +import net.corda.core.flows.StartableByRPC +import net.corda.core.flows.StartableByService +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.Party +import net.corda.core.node.services.vault.QueryCriteria +import net.corda.core.transactions.SignedTransaction +import com.r3.corda.lib.tokens.bridging.BridgeFungibleTokensFlow +import com.r3.corda.lib.tokens.bridging.contracts.BridgingContract +import com.r3.corda.lib.tokens.bridging.states.BridgedAssetLockState +import com.r3.corda.lib.tokens.contracts.types.TokenPointer +import net.corda.core.utilities.ProgressTracker +import net.corda.samples.stockpaydividend.flows.utilities.QueryUtilities +import net.corda.samples.stockpaydividend.states.StockState +import net.corda.solana.sdk.instruction.Pubkey + +@InitiatingFlow +@StartableByRPC +class BridgeStock( + val symbol: String, + val quantity: Long, + val bridgeAuthority: Party, + val destination: Pubkey, + val mint: Pubkey, + val mintAuthority: Pubkey +) : FlowLogic() { + + constructor( + symbol: String, + quantity: Long, + bridgeAuthority: Party, + destination: String, + mint: String, + mintAuthority: String + ) : this( + symbol, + quantity, + bridgeAuthority, + Pubkey.fromBase58(destination), + Pubkey.fromBase58(mint), + Pubkey.fromBase58(mintAuthority) + ) + + override val progressTracker = ProgressTracker() + + @Suspendable + override fun call(): String { + // To get the transferring stock, we can get the StockState from the vault and get it's pointer + val stockPointer: TokenPointer = QueryUtilities.queryStockPointer(symbol, serviceHub) + + // With the pointer, we can get the create an instance of transferring Amount + val amount: Amount = Amount(quantity, stockPointer) + + val additionalOutput: ContractState = BridgedAssetLockState(listOf(ourIdentity)) + val additionalCommand = BridgingContract.BridgingCommand.BridgeToSolana( + destination, + bridgeAuthority + ) //, ourIdentity.owningKey, bridgeAuthority.owningKey) + //Use built-in flow for move tokens to the recipient + val stx = subFlow( + BridgeFungibleTokens( + amount, + ourIdentity, + additionalOutput, + additionalCommand, + destination, + mint, + mintAuthority + ) + ) + + return ("\nBridged " + quantity + " " + symbol + " stocks to " + + ourIdentity.name.organisation + ".\nTransaction ID: " + stx.id) + } +} + +/** + * Initiating flow used to bridge amounts of tokens of the same party, [partyAndAmount] specifies what amount of tokens is bridged to a participant. + * + * Call this for one [TokenType] at a time. + * + * @param partyAndAmount pairing party - amount of token that is to be moved to that party + * @param observers optional observing parties to which the transaction will be broadcast + * @param queryCriteria additional criteria for token selection + */ +@StartableByService +@StartableByRPC +@InitiatingFlow +class BridgeFungibleTokens +@JvmOverloads +constructor( + val partyAndAmount: PartyAndAmount, + val observers: List = emptyList(), + val additionalOutput: ContractState, + val additionalCommand: BridgingContract.BridgingCommand, + val destination: Pubkey, + val mint: Pubkey, + val mintAuthority: Pubkey, + val queryCriteria: QueryCriteria? = null +) : FlowLogic() { + + constructor( + amount: Amount, holder: AbstractParty, additionalOutput: ContractState, + additionalCommand: BridgingContract.BridgingCommand, destination: Pubkey, mint: Pubkey, mintAuthority: Pubkey + ) + : this( + PartyAndAmount(holder, amount), + emptyList(), + additionalOutput, + additionalCommand, + destination, + mint, + mintAuthority + ) + + @Suspendable + override fun call(): SignedTransaction { + val participants = listOf(partyAndAmount.party) + val observerSessions = sessionsForParties(observers) + val participantSessions = sessionsForParties(participants) + return subFlow( + BridgeFungibleTokensFlow( + partyAndAmount = partyAndAmount, + participantSessions = participantSessions, + observerSessions = observerSessions, + additionalOutput = additionalOutput, + additionalCommand = additionalCommand, + destination = destination, + mint = mint, + mintAuthority = mintAuthority, + queryCriteria = queryCriteria + ) + ) + } +} + +/** + * Responder flow for [BridgeFungibleTokens]. + */ +@InitiatedBy(BridgeFungibleTokens::class) +class BridgeFungibleTokensHandler(val otherSession: FlowSession) : FlowLogic() { + @Suspendable + override fun call() = subFlow(MoveTokensFlowHandler(otherSession)) +} diff --git a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt index 5107ab77..f3de69fc 100644 --- a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt +++ b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt @@ -1,11 +1,13 @@ package net.corda.samples.stockpaydividend +import BridgeStock import com.r3.corda.lib.tokens.workflows.utilities.tokenBalance import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.samples.stockpaydividend.flows.* import net.corda.samples.stockpaydividend.states.StockState +import net.corda.solana.aggregator.common.Signer import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.TestIdentity import net.corda.testing.node.MockNetwork @@ -134,12 +136,20 @@ class FlowTests { Assert.assertEquals(issuerTx, observerTx) } - @Test @Throws(ExecutionException::class, InterruptedException::class) fun moveTest() { // Issue Stock - var future = company!!.startFlow(CreateAndIssueStock(STOCK_SYMBOL, STOCK_NAME, STOCK_CURRENCY, STOCK_PRICE, ISSUING_STOCK_QUANTITY, notaryParty!!)) + var future = company!!.startFlow( + CreateAndIssueStock( + STOCK_SYMBOL, + STOCK_NAME, + STOCK_CURRENCY, + STOCK_PRICE, + ISSUING_STOCK_QUANTITY, + notaryParty!! + ) + ) network!!.runNetwork() future.get() @@ -151,7 +161,11 @@ class FlowTests { //Retrieve states from receiver val receivedStockStatesPages = shareholder!!.services.vaultService.queryBy(StockState::class.java).states val receivedStockState = receivedStockStatesPages[0].state.data - val (quantity) = shareholder!!.services.vaultService.tokenBalance(receivedStockState.toPointer(receivedStockState.javaClass)) + val (quantity) = shareholder!!.services.vaultService.tokenBalance( + receivedStockState.toPointer( + receivedStockState.javaClass + ) + ) //Check Assert.assertEquals(quantity, java.lang.Long.valueOf(500).toLong()) @@ -165,4 +179,60 @@ class FlowTests { Assert.assertEquals(quantity1, java.lang.Long.valueOf(1500).toLong()) } + @Test + @Throws(ExecutionException::class, InterruptedException::class) + fun bridgeTest() { + + val mintAuthority = SolanaTestValidator.DEV_NOTARY + val accountOwner = Signer.random() + + testValidator.fundAccount(10, accountOwner) + + val tokenMint = testValidator.createToken(mintAuthority) + val tokenAccount = testValidator.createTokenAccount(accountOwner, tokenMint) + + // Issue Stock + var future = company!!.startFlow( + CreateAndIssueStock( + STOCK_SYMBOL, + STOCK_NAME, + STOCK_CURRENCY, + STOCK_PRICE, + ISSUING_STOCK_QUANTITY, + notaryParty!! + ) + ) + network!!.runNetwork() + future.get() + + val stockStatesPages = company!!.services.vaultService.queryBy(StockState::class.java).states + val stockState = stockStatesPages[0].state.data + val (quantity) = company!!.services.vaultService.tokenBalance(stockState.toPointer(stockState.javaClass)) + Assert.assertEquals(quantity, java.lang.Long.valueOf(2000).toLong()) + + // TODO Spend all to avoid having a change to yourself - then can't distinguish which amount si to mint which is a change + future = company!!.startFlow( + BridgeStock( + STOCK_SYMBOL, + ISSUING_STOCK_QUANTITY.toLong() /*BUYING_STOCK*/, + company!!.info.legalIdentities[0], + tokenAccount.base58(), + tokenMint.base58(), + mintAuthority.account.base58() + ) + ) + + network!!.runNetwork() + val result = future.get() + println(result) + + //Retrieve states from sender + val remainingStockStatesPages = company!!.services.vaultService.queryBy(StockState::class.java).states + val remainingStockState = remainingStockStatesPages[0].state.data + val (quantity1) = company!!.services.vaultService.tokenBalance(remainingStockState.toPointer(remainingStockState.javaClass)) + + //Check + Assert.assertEquals(quantity1, java.lang.Long.valueOf(2000).toLong()) + } + } \ No newline at end of file From d39ed5039341534eb8dc16707dc1bc849285c264 Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Wed, 3 Sep 2025 10:20:56 +0100 Subject: [PATCH 02/27] Reduce formatting diff --- .../samples/stockpaydividend/FlowTests.kt | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt index f3de69fc..602d8991 100644 --- a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt +++ b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt @@ -75,15 +75,17 @@ class FlowTests { @Before fun setup() { - network = MockNetwork(MockNetworkParameters(cordappsForAllNodes = listOf( - TestCordapp.findCordapp("net.corda.samples.stockpaydividend.contracts"), - TestCordapp.findCordapp("net.corda.samples.stockpaydividend.flows"), - TestCordapp.findCordapp("com.r3.corda.lib.tokens.contracts"), - TestCordapp.findCordapp("com.r3.corda.lib.tokens.workflows"), - DUMMY_CONTRACTS_CORDAPP - ), notarySpecs = listOf(MockNetworkNotarySpec(notaryName, notaryConfig = createNotaryConfig())), - networkParameters = testNetworkParameters(minimumPlatformVersion = 4) - ) + network = MockNetwork( + MockNetworkParameters( + cordappsForAllNodes = listOf( + TestCordapp.findCordapp("net.corda.samples.stockpaydividend.contracts"), + TestCordapp.findCordapp("net.corda.samples.stockpaydividend.flows"), + TestCordapp.findCordapp("com.r3.corda.lib.tokens.contracts"), + TestCordapp.findCordapp("com.r3.corda.lib.tokens.workflows"), + DUMMY_CONTRACTS_CORDAPP + ), notarySpecs = listOf(MockNetworkNotarySpec(notaryName, notaryConfig = createNotaryConfig())), + networkParameters = testNetworkParameters(minimumPlatformVersion = 4) + ) ) company = network!!.createPartyNode(COMPANY.name) @@ -136,6 +138,7 @@ class FlowTests { Assert.assertEquals(issuerTx, observerTx) } + @Test @Throws(ExecutionException::class, InterruptedException::class) fun moveTest() { From 0d422da6987645b35b40a59bfabe27d49097161a Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Wed, 3 Sep 2025 10:29:06 +0100 Subject: [PATCH 03/27] Fix class without a package --- .../kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt | 2 ++ .../test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt index 141e293d..76107ac1 100644 --- a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt +++ b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt @@ -1,3 +1,5 @@ +package com.r3.corda.lib.tokens.bridging.rpc + import co.paralleluniverse.fibers.Suspendable import com.r3.corda.lib.tokens.contracts.types.TokenType import com.r3.corda.lib.tokens.workflows.flows.move.MoveTokensFlowHandler diff --git a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt index 2247e2c5..eb7832b4 100644 --- a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt +++ b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt @@ -1,6 +1,6 @@ package net.corda.samples.stockpaydividend -import BridgeStock +import com.r3.corda.lib.tokens.bridging.rpc.BridgeStock import com.r3.corda.lib.tokens.workflows.utilities.tokenBalance import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name From f1ff37571aed502f57fe6d3e65c28252c0c98adc Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Mon, 22 Sep 2025 18:53:35 +0100 Subject: [PATCH 04/27] assert if Bridge state and token amount state are outputs of the same transaction --- .../samples/stockpaydividend/FlowTests.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt index eb7832b4..7c49b1ea 100644 --- a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt +++ b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt @@ -1,10 +1,17 @@ package net.corda.samples.stockpaydividend import com.r3.corda.lib.tokens.bridging.rpc.BridgeStock +import com.r3.corda.lib.tokens.bridging.states.BridgedAssetLockState +import com.r3.corda.lib.tokens.contracts.states.FungibleToken +import com.r3.corda.lib.tokens.contracts.types.TokenPointer +import com.r3.corda.lib.tokens.workflows.utilities.heldTokensByToken import com.r3.corda.lib.tokens.workflows.utilities.tokenBalance +import net.corda.core.contracts.StateAndRef +import net.corda.core.contracts.StateRef import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party +import net.corda.core.node.services.Vault import net.corda.samples.stockpaydividend.flows.* import net.corda.samples.stockpaydividend.states.StockState import net.corda.solana.aggregator.common.Signer @@ -245,6 +252,19 @@ class FlowTests { //Check Assert.assertEquals(quantity1, java.lang.Long.valueOf(2000).toLong()) + + val tokenPointer: TokenPointer = stockState.toPointer(stockState.javaClass) + val token: StateAndRef? = + company!!.services.vaultService.queryBy(FungibleToken::class.java).states.firstOrNull { it.state.data.amount.token.tokenType == tokenPointer } + Assert.assertNotNull(token) + val bridgingState: StateAndRef? = + company!!.services.vaultService.queryBy(BridgedAssetLockState::class.java).states.firstOrNull() + Assert.assertNotNull(bridgingState) + + Assert.assertTrue( + "Bridge state and token are outputs of the same transaction", + bridgingState!!.ref.txhash == token!!.ref.txhash + ) } } \ No newline at end of file From df088340442ff793519fe98bf8d0c35f773e29fa Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Mon, 22 Sep 2025 19:15:26 +0100 Subject: [PATCH 05/27] Remove use of addMoveFungibleTokens from tokens, temporally select first token to bridge instead --- .../bridging/BridgeFungibleTokensFlow.kt | 41 +++++++++++++++---- .../lib/tokens/bridging/rpc/BridgeTokens.kt | 7 +--- .../samples/stockpaydividend/FlowTests.kt | 3 -- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeFungibleTokensFlow.kt b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeFungibleTokensFlow.kt index f80a046d..3674559f 100644 --- a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeFungibleTokensFlow.kt +++ b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeFungibleTokensFlow.kt @@ -2,15 +2,24 @@ package com.r3.corda.lib.tokens.bridging import co.paralleluniverse.fibers.Suspendable import com.r3.corda.lib.tokens.bridging.contracts.BridgingContract +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.selector.DatabaseTokenSelection import com.r3.corda.lib.tokens.workflows.flows.move.AbstractMoveTokensFlow import com.r3.corda.lib.tokens.workflows.flows.move.addMoveFungibleTokens +import com.r3.corda.lib.tokens.workflows.flows.move.addMoveTokens import com.r3.corda.lib.tokens.workflows.types.PartyAndAmount +import net.corda.core.contracts.Amount +import net.corda.core.contracts.Amount.Companion.sumOrThrow import net.corda.core.contracts.ContractState +import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowSession +import net.corda.core.node.services.Vault import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.transactions.TransactionBuilder import net.corda.solana.sdk.instruction.Pubkey +import java.util.UUID //Follows flow moves fungible tokens and adds bridging command and output state to the transaction class BridgeFungibleTokensFlow @@ -23,19 +32,33 @@ constructor( val additionalCommand: BridgingContract.BridgingCommand, val destination: Pubkey, val mint: Pubkey, - val mintAuthority: Pubkey, - val queryCriteria: QueryCriteria? = null + val mintAuthority: Pubkey ) : AbstractMoveTokensFlow() { @Suspendable override fun addMove(transactionBuilder: TransactionBuilder) { - addMoveFungibleTokens( - transactionBuilder = transactionBuilder, - serviceHub = serviceHub, - partiesAndAmounts = listOf(partyAndAmount), //TODO change to Confidential Identity and change will be distributed to current holder (BI) - changeHolder = ourIdentity, - queryCriteria = queryCriteria - ) + + //TODO move to other flow + val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.UNCONSUMED) + + serviceHub.vaultService.queryBy(FungibleToken::class.java, criteria) + + val tokens = serviceHub.vaultService + .queryBy(FungibleToken::class.java, criteria) + .states +// .filter { stateAndRef -> +// val data = stateAndRef.state.data +// val token = data.amount.token // IssuedTokenType +// val tokenType = token.tokenType // TokenType +// tokenType == FiatCurrency.getInstance("GBP") && data.holder == ourIdentity +// // You can also check token.issuer == someIssuer if you want a specific issuer +// } + + val token = tokens[0].state.data + val holder = ourIdentity + val output = FungibleToken(token.amount, holder) + addMoveTokens(transactionBuilder = transactionBuilder, inputs = listOf(tokens[0]), outputs = listOf(output)) + val quantity = partyAndAmount.amount.toDecimal().toLong() //TODO bridgeToken( serviceHub, diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt index 76107ac1..2a0745ee 100644 --- a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt +++ b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt @@ -93,7 +93,6 @@ class BridgeStock( * * @param partyAndAmount pairing party - amount of token that is to be moved to that party * @param observers optional observing parties to which the transaction will be broadcast - * @param queryCriteria additional criteria for token selection */ @StartableByService @StartableByRPC @@ -107,8 +106,7 @@ constructor( val additionalCommand: BridgingContract.BridgingCommand, val destination: Pubkey, val mint: Pubkey, - val mintAuthority: Pubkey, - val queryCriteria: QueryCriteria? = null + val mintAuthority: Pubkey ) : FlowLogic() { constructor( @@ -139,8 +137,7 @@ constructor( additionalCommand = additionalCommand, destination = destination, mint = mint, - mintAuthority = mintAuthority, - queryCriteria = queryCriteria + mintAuthority = mintAuthority ) ) } diff --git a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt index 7c49b1ea..6fa2a8b6 100644 --- a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt +++ b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt @@ -4,14 +4,11 @@ import com.r3.corda.lib.tokens.bridging.rpc.BridgeStock import com.r3.corda.lib.tokens.bridging.states.BridgedAssetLockState import com.r3.corda.lib.tokens.contracts.states.FungibleToken import com.r3.corda.lib.tokens.contracts.types.TokenPointer -import com.r3.corda.lib.tokens.workflows.utilities.heldTokensByToken import com.r3.corda.lib.tokens.workflows.utilities.tokenBalance import net.corda.core.contracts.StateAndRef -import net.corda.core.contracts.StateRef import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party -import net.corda.core.node.services.Vault import net.corda.samples.stockpaydividend.flows.* import net.corda.samples.stockpaydividend.states.StockState import net.corda.solana.aggregator.common.Signer From 3a2a7fc53a6ad9a951f507932e49811ef11731f2 Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Mon, 22 Sep 2025 21:10:41 +0100 Subject: [PATCH 06/27] Move input token selection to outer flow, code simplification --- .../bridging/contracts/BridgingContract.kt | 5 +- .../bridging/BridgeFungibleTokensFlow.kt | 42 +++------------ .../lib/tokens/bridging/rpc/BridgeTokens.kt | 53 +++++++------------ 3 files changed, 31 insertions(+), 69 deletions(-) diff --git a/Tokens/stockpaydividend/contracts/src/main/kotlin/com/r3/corda/lib/tokens/bridging/contracts/BridgingContract.kt b/Tokens/stockpaydividend/contracts/src/main/kotlin/com/r3/corda/lib/tokens/bridging/contracts/BridgingContract.kt index fc9190dd..3577c93b 100644 --- a/Tokens/stockpaydividend/contracts/src/main/kotlin/com/r3/corda/lib/tokens/bridging/contracts/BridgingContract.kt +++ b/Tokens/stockpaydividend/contracts/src/main/kotlin/com/r3/corda/lib/tokens/bridging/contracts/BridgingContract.kt @@ -37,10 +37,11 @@ class BridgingContract : Contract { require(moveCommands.size == 1) { "Bridging must have one move command to lock token" } val lockedSum = tx.outputsOfType() - .filter { it.holder == bridgingCommand.bridgeAuthority } //TODO this is mute point for now, change to != bridgeAuthority, to filter only states owned by CI .. + .filter { it.holder == bridgingCommand.bridgeAuthority } // TODO this is mute point for now, change to != bridgeAuthority, to filter only states owned by CI ... + // ... currently can't distinguish between locked and a change, both are for same holder .sumOf { it.amount.toDecimal().toLong() - } // ... currently can't distinguish between locked and a change, both are for same holder ... + } val instruction = tx.notaryInstructions.singleOrNull() as? SolanaInstruction diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeFungibleTokensFlow.kt b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeFungibleTokensFlow.kt index 3674559f..84c93fcd 100644 --- a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeFungibleTokensFlow.kt +++ b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeFungibleTokensFlow.kt @@ -3,31 +3,20 @@ package com.r3.corda.lib.tokens.bridging import co.paralleluniverse.fibers.Suspendable import com.r3.corda.lib.tokens.bridging.contracts.BridgingContract 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.selector.DatabaseTokenSelection import com.r3.corda.lib.tokens.workflows.flows.move.AbstractMoveTokensFlow -import com.r3.corda.lib.tokens.workflows.flows.move.addMoveFungibleTokens import com.r3.corda.lib.tokens.workflows.flows.move.addMoveTokens -import com.r3.corda.lib.tokens.workflows.types.PartyAndAmount -import net.corda.core.contracts.Amount -import net.corda.core.contracts.Amount.Companion.sumOrThrow import net.corda.core.contracts.ContractState -import net.corda.core.flows.FlowLogic +import net.corda.core.contracts.StateAndRef import net.corda.core.flows.FlowSession -import net.corda.core.node.services.Vault -import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.transactions.TransactionBuilder import net.corda.solana.sdk.instruction.Pubkey -import java.util.UUID -//Follows flow moves fungible tokens and adds bridging command and output state to the transaction class BridgeFungibleTokensFlow @JvmOverloads constructor( - val partyAndAmount: PartyAndAmount, override val participantSessions: List, override val observerSessions: List = emptyList(), + val token: StateAndRef, val additionalOutput: ContractState, val additionalCommand: BridgingContract.BridgingCommand, val destination: Pubkey, @@ -38,28 +27,13 @@ constructor( @Suspendable override fun addMove(transactionBuilder: TransactionBuilder) { - //TODO move to other flow - val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.UNCONSUMED) + val amount = token.state.data.amount + val holder = ourIdentity //TODO confidential identity + val output = FungibleToken(amount, holder) + addMoveTokens(transactionBuilder = transactionBuilder, inputs = listOf(token), outputs = listOf(output)) - serviceHub.vaultService.queryBy(FungibleToken::class.java, criteria) - - val tokens = serviceHub.vaultService - .queryBy(FungibleToken::class.java, criteria) - .states -// .filter { stateAndRef -> -// val data = stateAndRef.state.data -// val token = data.amount.token // IssuedTokenType -// val tokenType = token.tokenType // TokenType -// tokenType == FiatCurrency.getInstance("GBP") && data.holder == ourIdentity -// // You can also check token.issuer == someIssuer if you want a specific issuer -// } - - val token = tokens[0].state.data - val holder = ourIdentity - val output = FungibleToken(token.amount, holder) - addMoveTokens(transactionBuilder = transactionBuilder, inputs = listOf(tokens[0]), outputs = listOf(output)) - - val quantity = partyAndAmount.amount.toDecimal().toLong() //TODO + val quantity = amount.toDecimal() + .toLong() //TODO this is quantity for Solana, should it be 1 to 1 what is bridged on Corda? bridgeToken( serviceHub, transactionBuilder, diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt index 2a0745ee..ef3523b0 100644 --- a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt +++ b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt @@ -3,9 +3,7 @@ package com.r3.corda.lib.tokens.bridging.rpc import co.paralleluniverse.fibers.Suspendable import com.r3.corda.lib.tokens.contracts.types.TokenType import com.r3.corda.lib.tokens.workflows.flows.move.MoveTokensFlowHandler -import com.r3.corda.lib.tokens.workflows.types.PartyAndAmount import com.r3.corda.lib.tokens.workflows.utilities.sessionsForParties -import net.corda.core.contracts.Amount import net.corda.core.contracts.ContractState import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowSession @@ -20,10 +18,10 @@ import net.corda.core.transactions.SignedTransaction import com.r3.corda.lib.tokens.bridging.BridgeFungibleTokensFlow import com.r3.corda.lib.tokens.bridging.contracts.BridgingContract import com.r3.corda.lib.tokens.bridging.states.BridgedAssetLockState -import com.r3.corda.lib.tokens.contracts.types.TokenPointer +import com.r3.corda.lib.tokens.contracts.states.FungibleToken +import net.corda.core.contracts.StateAndRef +import net.corda.core.node.services.Vault import net.corda.core.utilities.ProgressTracker -import net.corda.samples.stockpaydividend.flows.utilities.QueryUtilities -import net.corda.samples.stockpaydividend.states.StockState import net.corda.solana.sdk.instruction.Pubkey @InitiatingFlow @@ -57,22 +55,16 @@ class BridgeStock( @Suspendable override fun call(): String { - // To get the transferring stock, we can get the StockState from the vault and get it's pointer - val stockPointer: TokenPointer = QueryUtilities.queryStockPointer(symbol, serviceHub) - - // With the pointer, we can get the create an instance of transferring Amount - val amount: Amount = Amount(quantity, stockPointer) - val additionalOutput: ContractState = BridgedAssetLockState(listOf(ourIdentity)) val additionalCommand = BridgingContract.BridgingCommand.BridgeToSolana( destination, bridgeAuthority - ) //, ourIdentity.owningKey, bridgeAuthority.owningKey) + ) //Use built-in flow for move tokens to the recipient val stx = subFlow( BridgeFungibleTokens( - amount, - ourIdentity, + ourIdentity, //TODO confidentialIdentity + emptyList(), additionalOutput, additionalCommand, destination, @@ -87,11 +79,10 @@ class BridgeStock( } /** - * Initiating flow used to bridge amounts of tokens of the same party, [partyAndAmount] specifies what amount of tokens is bridged to a participant. + * Initiating flow used to bridge amounts of tokens of the same party. * * Call this for one [TokenType] at a time. * - * @param partyAndAmount pairing party - amount of token that is to be moved to that party * @param observers optional observing parties to which the transaction will be broadcast */ @StartableByService @@ -100,7 +91,7 @@ class BridgeStock( class BridgeFungibleTokens @JvmOverloads constructor( - val partyAndAmount: PartyAndAmount, + val holder: AbstractParty, val observers: List = emptyList(), val additionalOutput: ContractState, val additionalCommand: BridgingContract.BridgingCommand, @@ -109,30 +100,26 @@ constructor( val mintAuthority: Pubkey ) : FlowLogic() { - constructor( - amount: Amount, holder: AbstractParty, additionalOutput: ContractState, - additionalCommand: BridgingContract.BridgingCommand, destination: Pubkey, mint: Pubkey, mintAuthority: Pubkey - ) - : this( - PartyAndAmount(holder, amount), - emptyList(), - additionalOutput, - additionalCommand, - destination, - mint, - mintAuthority - ) - @Suspendable override fun call(): SignedTransaction { - val participants = listOf(partyAndAmount.party) + val participants = listOf(holder) //TODO add confidentialIdentity val observerSessions = sessionsForParties(observers) val participantSessions = sessionsForParties(participants) + + //TODO add list of StetRef to bridge in flow parameters + val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.UNCONSUMED) + + serviceHub.vaultService.queryBy(FungibleToken::class.java, criteria) + + val token: StateAndRef = serviceHub.vaultService + .queryBy(FungibleToken::class.java, criteria) + .states.first() + return subFlow( BridgeFungibleTokensFlow( - partyAndAmount = partyAndAmount, participantSessions = participantSessions, observerSessions = observerSessions, + token = token, additionalOutput = additionalOutput, additionalCommand = additionalCommand, destination = destination, From 24ef0676baec360418effcd2dea9124e21f0d8b3 Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Tue, 23 Sep 2025 13:13:29 +0100 Subject: [PATCH 07/27] Additional Assertion on token balance (WIP) --- .../net/corda/samples/stockpaydividend/FlowTests.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt index 6fa2a8b6..6c4e0bd3 100644 --- a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt +++ b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt @@ -11,7 +11,9 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.samples.stockpaydividend.flows.* import net.corda.samples.stockpaydividend.states.StockState +import net.corda.solana.aggregator.common.RpcParams import net.corda.solana.aggregator.common.Signer +import net.corda.solana.aggregator.common.checkResponse import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.TestIdentity import net.corda.testing.node.MockNetwork @@ -226,6 +228,9 @@ class FlowTests { val (quantity) = company!!.services.vaultService.tokenBalance(stockState.toPointer(stockState.javaClass)) Assert.assertEquals(quantity, java.lang.Long.valueOf(2000).toLong()) + val startSolanaBalance = + testValidator.client.getBalance(accountOwner.account.base58(), RpcParams()).checkResponse("getBalance") + // TODO Spend all to avoid having a change to yourself - then can't distinguish which amount si to mint which is a change future = company!!.startFlow( BridgeStock( @@ -262,6 +267,11 @@ class FlowTests { "Bridge state and token are outputs of the same transaction", bridgingState!!.ref.txhash == token!!.ref.txhash ) + + val endSolanaBalance = + testValidator.client.getBalance(accountOwner.account.base58(), RpcParams()).checkResponse("getBalance") + + Assert.assertEquals(startSolanaBalance, endSolanaBalance) //TODO check token account not total token balance } } \ No newline at end of file From ce6fbd263845ddd7865816e665c68e9f7f14e58c Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Tue, 23 Sep 2025 16:34:09 +0100 Subject: [PATCH 08/27] Additional Assertion on token balance in Solana network, and final balance in Corda network --- .../samples/stockpaydividend/FlowTests.kt | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt index 6c4e0bd3..f216dea2 100644 --- a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt +++ b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt @@ -225,11 +225,14 @@ class FlowTests { val stockStatesPages = company!!.services.vaultService.queryBy(StockState::class.java).states val stockState = stockStatesPages[0].state.data - val (quantity) = company!!.services.vaultService.tokenBalance(stockState.toPointer(stockState.javaClass)) - Assert.assertEquals(quantity, java.lang.Long.valueOf(2000).toLong()) + val stockStatePointer = stockState.toPointer(stockState.javaClass) + val (startCordaQuantity) = company!!.services.vaultService.tokenBalance(stockStatePointer) + Assert.assertEquals(2000L, startCordaQuantity) val startSolanaBalance = - testValidator.client.getBalance(accountOwner.account.base58(), RpcParams()).checkResponse("getBalance") + testValidator.client.getTokenAccountBalance(tokenAccount.base58(), RpcParams()) + .checkResponse("getTokenAccountBalance") + Assert.assertEquals("0", startSolanaBalance!!.amount) // TODO Spend all to avoid having a change to yourself - then can't distinguish which amount si to mint which is a change future = company!!.startFlow( @@ -247,12 +250,10 @@ class FlowTests { val result = future.get() println(result) - //Retrieve states from sender val remainingStockStatesPages = company!!.services.vaultService.queryBy(StockState::class.java).states val remainingStockState = remainingStockStatesPages[0].state.data val (quantity1) = company!!.services.vaultService.tokenBalance(remainingStockState.toPointer(remainingStockState.javaClass)) - //Check Assert.assertEquals(quantity1, java.lang.Long.valueOf(2000).toLong()) val tokenPointer: TokenPointer = stockState.toPointer(stockState.javaClass) @@ -268,10 +269,17 @@ class FlowTests { bridgingState!!.ref.txhash == token!!.ref.txhash ) - val endSolanaBalance = - testValidator.client.getBalance(accountOwner.account.base58(), RpcParams()).checkResponse("getBalance") + val (finalCordaQuantity) = company!!.services.vaultService.tokenBalance(stockStatePointer) + Assert.assertEquals( + 2000L, + finalCordaQuantity + ) //TODO this is Corda move token to self, so it still the same amount as at the beginning + + val finalSolanaBalance = + testValidator.client.getTokenAccountBalance(tokenAccount.base58(), RpcParams()) + .checkResponse("getTokenAccountBalance") - Assert.assertEquals(startSolanaBalance, endSolanaBalance) //TODO check token account not total token balance + Assert.assertEquals("2000", finalSolanaBalance!!.amount) } } \ No newline at end of file From e73a853c45c205cd32a16bb38ea1f07405593442 Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Wed, 24 Sep 2025 10:15:56 +0100 Subject: [PATCH 09/27] Update to 4.14 --- Tokens/constants.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tokens/constants.properties b/Tokens/constants.properties index 4b7e6378..8b66881f 100644 --- a/Tokens/constants.properties +++ b/Tokens/constants.properties @@ -1,7 +1,7 @@ cordaReleaseGroup=com.r3.corda cordaCoreReleaseGroup=net.corda -cordaVersion=4.13-SNAPSHOT -cordaCoreVersion=4.13-SNAPSHOT +cordaVersion=4.14-SNAPSHOT +cordaCoreVersion=4.14-SNAPSHOT gradlePluginsVersion=5.1.1 kotlinVersion=1.9.20 junitVersion=4.12 From 20f1a8cebc322162bd13ba330dd13489f618d249 Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Wed, 24 Sep 2025 11:17:07 +0100 Subject: [PATCH 10/27] Update to change from ENT-14126 SolanaTestValidator usable in user CordDapp testing --- .../samples/stockpaydividend/FlowTests.kt | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt index f216dea2..f7a340fa 100644 --- a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt +++ b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt @@ -14,6 +14,7 @@ import net.corda.samples.stockpaydividend.states.StockState import net.corda.solana.aggregator.common.RpcParams import net.corda.solana.aggregator.common.Signer import net.corda.solana.aggregator.common.checkResponse +import net.corda.solana.sdk.internal.Token2022 import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.TestIdentity import net.corda.testing.node.MockNetwork @@ -32,6 +33,10 @@ import java.util.* import java.util.concurrent.ExecutionException import net.corda.testing.node.MockNetworkNotarySpec import net.corda.testing.node.internal.DUMMY_CONTRACTS_CORDAPP +import net.corda.testing.solana.randomKeypairFile +import org.junit.ClassRule +import org.junit.rules.TemporaryFolder +import java.nio.file.Path class FlowTests { private var network: MockNetwork? = null @@ -57,17 +62,35 @@ class FlowTests { val ISSUING_STOCK_QUANTITY = 2000 val BUYING_STOCK = java.lang.Long.valueOf(500) + companion object { val notaryName = CordaX500Name("Solana Notary Service", "Zurich", "CH") + @ClassRule + @JvmField + val generalDir = TemporaryFolder() + + @ClassRule + @JvmField + val custodiedKeysDir = TemporaryFolder() + + private lateinit var notaryKeyFile: Path + private lateinit var notaryKey: Signer + private lateinit var mintAuthority: Signer + private val tokenAccountOwner = Signer.random() private lateinit var testValidator: SolanaTestValidator @BeforeClass @JvmStatic fun startTestValidator() { testValidator = SolanaTestValidator() - testValidator.fundDevAccounts() - testValidator.defaultNotarySetup() + notaryKeyFile = generalDir.randomKeypairFile() + notaryKey = Signer.fromFile(notaryKeyFile) + mintAuthority = Signer.fromFile(custodiedKeysDir.randomKeypairFile()) + testValidator.start() + testValidator.defaultNotaryProgramSetup(notaryKey.account) + testValidator.fundAccount(10, mintAuthority) + testValidator.fundAccount(10, tokenAccountOwner) } @AfterClass @@ -122,10 +145,14 @@ class FlowTests { notaryLegalIdentity = "$notaryName" solana { rpcUrl = "${SolanaTestValidator.RPC_URL}" - wallet = "${SolanaTestValidator.DEV_NOTARY_FILE}" + notaryKeypairFile = "$notaryKeyFile" + custodiedKeysDir = "${custodiedKeysDir.root.toPath()}" + programWhitelist = ["${Token2022.PROGRAM_ID}"] + } """.trimIndent() + //wallet = "${SolanaTestValidator.DEV_NOTARY_FILE}" @Test @Throws(ExecutionException::class, InterruptedException::class) fun issueTest() { @@ -201,7 +228,6 @@ class FlowTests { @Throws(ExecutionException::class, InterruptedException::class) fun bridgeTest() { - val mintAuthority = SolanaTestValidator.DEV_NOTARY val accountOwner = Signer.random() testValidator.fundAccount(10, accountOwner) From c5afeb4bb7c13775f585520d7944d448944b183d Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Wed, 24 Sep 2025 11:18:15 +0100 Subject: [PATCH 11/27] Remove println --- .../kotlin/net/corda/samples/stockpaydividend/FlowTests.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt index f7a340fa..c81c6970 100644 --- a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt +++ b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt @@ -273,8 +273,7 @@ class FlowTests { ) network!!.runNetwork() - val result = future.get() - println(result) + future.get() val remainingStockStatesPages = company!!.services.vaultService.queryBy(StockState::class.java).states val remainingStockState = remainingStockStatesPages[0].state.data From 87238d2ff289d4047c3a0d102d86b55988d2f7e7 Mon Sep 17 00:00:00 2001 From: Michal Kit Date: Wed, 24 Sep 2025 11:47:55 +0100 Subject: [PATCH 12/27] Minor changes --- .../kotlin/net/corda/samples/stockpaydividend/FlowTests.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt index c81c6970..a5fa0dc3 100644 --- a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt +++ b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt @@ -253,7 +253,7 @@ class FlowTests { val stockState = stockStatesPages[0].state.data val stockStatePointer = stockState.toPointer(stockState.javaClass) val (startCordaQuantity) = company!!.services.vaultService.tokenBalance(stockStatePointer) - Assert.assertEquals(2000L, startCordaQuantity) + Assert.assertEquals(ISSUING_STOCK_QUANTITY, startCordaQuantity) val startSolanaBalance = testValidator.client.getTokenAccountBalance(tokenAccount.base58(), RpcParams()) @@ -264,7 +264,7 @@ class FlowTests { future = company!!.startFlow( BridgeStock( STOCK_SYMBOL, - ISSUING_STOCK_QUANTITY.toLong() /*BUYING_STOCK*/, + startCordaQuantity , company!!.info.legalIdentities[0], tokenAccount.base58(), tokenMint.base58(), @@ -279,7 +279,7 @@ class FlowTests { val remainingStockState = remainingStockStatesPages[0].state.data val (quantity1) = company!!.services.vaultService.tokenBalance(remainingStockState.toPointer(remainingStockState.javaClass)) - Assert.assertEquals(quantity1, java.lang.Long.valueOf(2000).toLong()) + Assert.assertEquals(2000L, quantity1) val tokenPointer: TokenPointer = stockState.toPointer(stockState.javaClass) val token: StateAndRef? = From e515d7d0795919beb18dadef8c141e5c9c1d701d Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Wed, 24 Sep 2025 12:00:02 +0100 Subject: [PATCH 13/27] remove some duplicated checks in the test, tidy up --- .../samples/stockpaydividend/FlowTests.kt | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt index a5fa0dc3..ac11ed62 100644 --- a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt +++ b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt @@ -250,10 +250,11 @@ class FlowTests { future.get() val stockStatesPages = company!!.services.vaultService.queryBy(StockState::class.java).states + Assert.assertNotNull(stockStatesPages[0]) val stockState = stockStatesPages[0].state.data val stockStatePointer = stockState.toPointer(stockState.javaClass) val (startCordaQuantity) = company!!.services.vaultService.tokenBalance(stockStatePointer) - Assert.assertEquals(ISSUING_STOCK_QUANTITY, startCordaQuantity) + Assert.assertEquals(ISSUING_STOCK_QUANTITY.toLong(), startCordaQuantity) val startSolanaBalance = testValidator.client.getTokenAccountBalance(tokenAccount.base58(), RpcParams()) @@ -276,14 +277,20 @@ class FlowTests { future.get() val remainingStockStatesPages = company!!.services.vaultService.queryBy(StockState::class.java).states + Assert.assertNotNull(remainingStockStatesPages[0]) val remainingStockState = remainingStockStatesPages[0].state.data - val (quantity1) = company!!.services.vaultService.tokenBalance(remainingStockState.toPointer(remainingStockState.javaClass)) - - Assert.assertEquals(2000L, quantity1) + val remainingStockStatePointer: TokenPointer = + remainingStockState.toPointer(remainingStockState.javaClass) + val (finalCordaQuantity) = company!!.services.vaultService.tokenBalance(remainingStockStatePointer) + Assert.assertEquals( + ISSUING_STOCK_QUANTITY.toLong(), + finalCordaQuantity + ) // TODO this is Corda move token to self, so it still the same amount as at the beginning - val tokenPointer: TokenPointer = stockState.toPointer(stockState.javaClass) val token: StateAndRef? = - company!!.services.vaultService.queryBy(FungibleToken::class.java).states.firstOrNull { it.state.data.amount.token.tokenType == tokenPointer } + company!!.services.vaultService.queryBy(FungibleToken::class.java).states.firstOrNull { + it.state.data.amount.token.tokenType == remainingStockStatePointer + } Assert.assertNotNull(token) val bridgingState: StateAndRef? = company!!.services.vaultService.queryBy(BridgedAssetLockState::class.java).states.firstOrNull() @@ -294,17 +301,11 @@ class FlowTests { bridgingState!!.ref.txhash == token!!.ref.txhash ) - val (finalCordaQuantity) = company!!.services.vaultService.tokenBalance(stockStatePointer) - Assert.assertEquals( - 2000L, - finalCordaQuantity - ) //TODO this is Corda move token to self, so it still the same amount as at the beginning - val finalSolanaBalance = testValidator.client.getTokenAccountBalance(tokenAccount.base58(), RpcParams()) .checkResponse("getTokenAccountBalance") - Assert.assertEquals("2000", finalSolanaBalance!!.amount) + Assert.assertEquals(ISSUING_STOCK_QUANTITY.toString(), finalSolanaBalance!!.amount) } } \ No newline at end of file From 236cc51633843450c318564ae97ac3c486f04175 Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Thu, 25 Sep 2025 17:10:00 +0100 Subject: [PATCH 14/27] Corda participant to Solana issuance address configuration and other mappings, via Cordapp config file --- .../bridging/SolanaAccountsMappingService.kt | 42 ++++++++++++++++++ .../lib/tokens/bridging/rpc/BridgeTokens.kt | 27 +++--------- .../samples/stockpaydividend/FlowTests.kt | 43 ++++++++++++------- 3 files changed, 76 insertions(+), 36 deletions(-) create mode 100644 Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/SolanaAccountsMappingService.kt diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/SolanaAccountsMappingService.kt b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/SolanaAccountsMappingService.kt new file mode 100644 index 00000000..4c081b2d --- /dev/null +++ b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/SolanaAccountsMappingService.kt @@ -0,0 +1,42 @@ +package com.r3.corda.lib.tokens.bridging + +import net.corda.core.identity.CordaX500Name +import net.corda.core.node.AppServiceHub +import net.corda.core.node.services.CordaService +import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.solana.sdk.instruction.Pubkey + +@CordaService +class SolanaAccountsMappingService(appServiceHub: AppServiceHub) : SingletonSerializeAsToken() { + var participants: Map + var mints: Map + var minAuthorities: Map + + // TODO quiet failover as this service is used in MockNetwork by nodes without a config for now + // will be fail fast after separating flows to specific CordApps and adding Birding Authority which will have exclusive flows and this service + init { + val cfg = appServiceHub.getAppContext().config + participants = try { + (cfg.get("participants") as? Map)?.map { (k, v) -> + CordaX500Name.parse(k) to Pubkey.fromBase58( + v + ) + }?.toMap() ?: emptyMap() + } catch (_: Exception) { + emptyMap() // TODO see above + } + mints = try { + (cfg.get("mints") as? Map)?.map { (k, v) -> k to Pubkey.fromBase58(v) }?.toMap() + ?: emptyMap() + } catch (_: Exception) { + emptyMap() // TODO see above + } + + minAuthorities = try { + (cfg.get("mintAuthorities") as? Map)?.map { (k, v) -> k to Pubkey.fromBase58(v) }?.toMap() + ?: emptyMap() + } catch (_: Exception) { + emptyMap() // TODO see above + } + } +} \ No newline at end of file diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt index ef3523b0..a832434c 100644 --- a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt +++ b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt @@ -16,6 +16,7 @@ import net.corda.core.identity.Party import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.transactions.SignedTransaction import com.r3.corda.lib.tokens.bridging.BridgeFungibleTokensFlow +import com.r3.corda.lib.tokens.bridging.SolanaAccountsMappingService import com.r3.corda.lib.tokens.bridging.contracts.BridgingContract import com.r3.corda.lib.tokens.bridging.states.BridgedAssetLockState import com.r3.corda.lib.tokens.contracts.states.FungibleToken @@ -29,33 +30,19 @@ import net.corda.solana.sdk.instruction.Pubkey class BridgeStock( val symbol: String, val quantity: Long, - val bridgeAuthority: Party, - val destination: Pubkey, - val mint: Pubkey, - val mintAuthority: Pubkey + val bridgeAuthority: Party ) : FlowLogic() { - constructor( - symbol: String, - quantity: Long, - bridgeAuthority: Party, - destination: String, - mint: String, - mintAuthority: String - ) : this( - symbol, - quantity, - bridgeAuthority, - Pubkey.fromBase58(destination), - Pubkey.fromBase58(mint), - Pubkey.fromBase58(mintAuthority) - ) - override val progressTracker = ProgressTracker() @Suspendable override fun call(): String { val additionalOutput: ContractState = BridgedAssetLockState(listOf(ourIdentity)) + + val solanaAccountMapping = serviceHub.cordaService(SolanaAccountsMappingService::class.java) + val destination = solanaAccountMapping.participants[ourIdentity.name]!! //TODO handle null + val mint = solanaAccountMapping.mints[symbol]!! //TODO handle null + val mintAuthority = solanaAccountMapping.minAuthorities[symbol]!! //TODO handle null val additionalCommand = BridgingContract.BridgingCommand.BridgeToSolana( destination, bridgeAuthority diff --git a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt index ac11ed62..d2555e54 100644 --- a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt +++ b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt @@ -1,5 +1,6 @@ package net.corda.samples.stockpaydividend +import com.lmax.solana4j.api.PublicKey import com.r3.corda.lib.tokens.bridging.rpc.BridgeStock import com.r3.corda.lib.tokens.bridging.states.BridgedAssetLockState import com.r3.corda.lib.tokens.contracts.states.FungibleToken @@ -32,6 +33,7 @@ import java.math.BigDecimal import java.util.* import java.util.concurrent.ExecutionException import net.corda.testing.node.MockNetworkNotarySpec +import net.corda.testing.node.MockNodeParameters import net.corda.testing.node.internal.DUMMY_CONTRACTS_CORDAPP import net.corda.testing.solana.randomKeypairFile import org.junit.ClassRule @@ -80,6 +82,9 @@ class FlowTests { private val tokenAccountOwner = Signer.random() private lateinit var testValidator: SolanaTestValidator + private lateinit var tokenMint: PublicKey + private lateinit var tokenAccount: PublicKey + @BeforeClass @JvmStatic fun startTestValidator() { @@ -91,6 +96,13 @@ class FlowTests { testValidator.defaultNotaryProgramSetup(notaryKey.account) testValidator.fundAccount(10, mintAuthority) testValidator.fundAccount(10, tokenAccountOwner) + + val accountOwner = Signer.random() + + testValidator.fundAccount(10, accountOwner) + + tokenMint = testValidator.createToken(mintAuthority) + tokenAccount = testValidator.createTokenAccount(accountOwner, tokenMint) } @AfterClass @@ -104,11 +116,12 @@ class FlowTests { @Before fun setup() { + val app = TestCordapp.findCordapp("net.corda.samples.stockpaydividend.flows") network = MockNetwork( MockNetworkParameters( cordappsForAllNodes = listOf( TestCordapp.findCordapp("net.corda.samples.stockpaydividend.contracts"), - TestCordapp.findCordapp("net.corda.samples.stockpaydividend.flows"), + app, TestCordapp.findCordapp("com.r3.corda.lib.tokens.contracts"), TestCordapp.findCordapp("com.r3.corda.lib.tokens.workflows"), DUMMY_CONTRACTS_CORDAPP @@ -117,7 +130,17 @@ class FlowTests { ) ) - company = network!!.createPartyNode(COMPANY.name) + val companyCfg = mapOf( + "participants" to mapOf(COMPANY.name.toString() to tokenAccount.base58()), + "mints" to mapOf(STOCK_SYMBOL to tokenMint.base58()), + "mintAuthorities" to mapOf(STOCK_SYMBOL to mintAuthority.account.base58()) + ) + company = network!!.createNode( + MockNodeParameters( + legalName = COMPANY.name, + additionalCordapps = listOf(app.withConfig(companyCfg)) + ) + ) observer = network!!.createPartyNode(OBSERVER.name) shareholder = network!!.createPartyNode(SHAREHOLDER.name) bank = network!!.createPartyNode(BANK.name) @@ -147,12 +170,10 @@ class FlowTests { rpcUrl = "${SolanaTestValidator.RPC_URL}" notaryKeypairFile = "$notaryKeyFile" custodiedKeysDir = "${custodiedKeysDir.root.toPath()}" - programWhitelist = ["${Token2022.PROGRAM_ID}"] - + programWhitelist = ["${Token2022.PROGRAM_ID}"] } """.trimIndent() - //wallet = "${SolanaTestValidator.DEV_NOTARY_FILE}" @Test @Throws(ExecutionException::class, InterruptedException::class) fun issueTest() { @@ -228,13 +249,6 @@ class FlowTests { @Throws(ExecutionException::class, InterruptedException::class) fun bridgeTest() { - val accountOwner = Signer.random() - - testValidator.fundAccount(10, accountOwner) - - val tokenMint = testValidator.createToken(mintAuthority) - val tokenAccount = testValidator.createTokenAccount(accountOwner, tokenMint) - // Issue Stock var future = company!!.startFlow( CreateAndIssueStock( @@ -266,10 +280,7 @@ class FlowTests { BridgeStock( STOCK_SYMBOL, startCordaQuantity , - company!!.info.legalIdentities[0], - tokenAccount.base58(), - tokenMint.base58(), - mintAuthority.account.base58() + company!!.info.legalIdentities[0] ) ) From 3550a5c95ac823f9e6c150c6cfa7fe8e80ce51d1 Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Fri, 26 Sep 2025 10:16:59 +0100 Subject: [PATCH 15/27] Addressing review comments --- .../tokens/bridging/SolanaAccountsMappingService.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/SolanaAccountsMappingService.kt b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/SolanaAccountsMappingService.kt index 4c081b2d..5cf3903e 100644 --- a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/SolanaAccountsMappingService.kt +++ b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/SolanaAccountsMappingService.kt @@ -10,7 +10,7 @@ import net.corda.solana.sdk.instruction.Pubkey class SolanaAccountsMappingService(appServiceHub: AppServiceHub) : SingletonSerializeAsToken() { var participants: Map var mints: Map - var minAuthorities: Map + var mintAuthorities: Map // TODO quiet failover as this service is used in MockNetwork by nodes without a config for now // will be fail fast after separating flows to specific CordApps and adding Birding Authority which will have exclusive flows and this service @@ -23,20 +23,20 @@ class SolanaAccountsMappingService(appServiceHub: AppServiceHub) : SingletonSeri ) }?.toMap() ?: emptyMap() } catch (_: Exception) { - emptyMap() // TODO see above + emptyMap() } mints = try { (cfg.get("mints") as? Map)?.map { (k, v) -> k to Pubkey.fromBase58(v) }?.toMap() ?: emptyMap() } catch (_: Exception) { - emptyMap() // TODO see above + emptyMap() } - minAuthorities = try { + mintAuthorities = try { (cfg.get("mintAuthorities") as? Map)?.map { (k, v) -> k to Pubkey.fromBase58(v) }?.toMap() ?: emptyMap() } catch (_: Exception) { - emptyMap() // TODO see above + emptyMap() } } } \ No newline at end of file From 7f6676baa05d20975cabb4fa2e239409fe8e869c Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Fri, 26 Sep 2025 15:32:15 +0100 Subject: [PATCH 16/27] Initial refactoring before changing mapping key. --- .../lib/tokens/bridging/rpc/BridgeTokens.kt | 47 ++++++++----------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt index a832434c..beb3a7e5 100644 --- a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt +++ b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt @@ -37,26 +37,20 @@ class BridgeStock( @Suspendable override fun call(): String { - val additionalOutput: ContractState = BridgedAssetLockState(listOf(ourIdentity)) - val solanaAccountMapping = serviceHub.cordaService(SolanaAccountsMappingService::class.java) - val destination = solanaAccountMapping.participants[ourIdentity.name]!! //TODO handle null - val mint = solanaAccountMapping.mints[symbol]!! //TODO handle null - val mintAuthority = solanaAccountMapping.minAuthorities[symbol]!! //TODO handle null - val additionalCommand = BridgingContract.BridgingCommand.BridgeToSolana( - destination, - bridgeAuthority - ) + //TODO add list of StetRef to bridge in flow parameters + val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.UNCONSUMED) + val token: StateAndRef = serviceHub.vaultService + .queryBy(FungibleToken::class.java, criteria) + .states.first() + //Use built-in flow for move tokens to the recipient val stx = subFlow( BridgeFungibleTokens( ourIdentity, //TODO confidentialIdentity emptyList(), - additionalOutput, - additionalCommand, - destination, - mint, - mintAuthority + token, + bridgeAuthority ) ) @@ -73,18 +67,14 @@ class BridgeStock( * @param observers optional observing parties to which the transaction will be broadcast */ @StartableByService -@StartableByRPC @InitiatingFlow class BridgeFungibleTokens @JvmOverloads constructor( val holder: AbstractParty, val observers: List = emptyList(), - val additionalOutput: ContractState, - val additionalCommand: BridgingContract.BridgingCommand, - val destination: Pubkey, - val mint: Pubkey, - val mintAuthority: Pubkey + val token: StateAndRef, //TODO should be FungibleToken + val bridgeAuthority: Party ) : FlowLogic() { @Suspendable @@ -93,14 +83,17 @@ constructor( val observerSessions = sessionsForParties(observers) val participantSessions = sessionsForParties(participants) - //TODO add list of StetRef to bridge in flow parameters - val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.UNCONSUMED) - - serviceHub.vaultService.queryBy(FungibleToken::class.java, criteria) + val additionalOutput: ContractState = BridgedAssetLockState(listOf(ourIdentity)) - val token: StateAndRef = serviceHub.vaultService - .queryBy(FungibleToken::class.java, criteria) - .states.first() + val symbol = "TEST" //TODO mapping should change to LinearId + val solanaAccountMapping = serviceHub.cordaService(SolanaAccountsMappingService::class.java) + val destination = solanaAccountMapping.participants[ourIdentity.name]!! //TODO handle null + val mint = solanaAccountMapping.mints[symbol]!! //TODO handle null + val mintAuthority = solanaAccountMapping.mintAuthorities[symbol]!! //TODO handle null + val additionalCommand = BridgingContract.BridgingCommand.BridgeToSolana( + destination, + bridgeAuthority + ) return subFlow( BridgeFungibleTokensFlow( From dec3caee3c0b54cc74175d4fd84c9100e1b816cd Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Fri, 26 Sep 2025 16:50:14 +0100 Subject: [PATCH 17/27] Use FungibleToken and LinearId for mappings (instead of stock ticket). --- .../bridging/SolanaAccountsMappingService.kt | 14 ++++++++++---- .../corda/lib/tokens/bridging/rpc/BridgeTokens.kt | 15 ++++++++------- .../stockpaydividend/flows/CreateAndIssueStock.kt | 9 ++++----- .../corda/samples/stockpaydividend/FlowTests.kt | 11 +++++++---- 4 files changed, 29 insertions(+), 20 deletions(-) diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/SolanaAccountsMappingService.kt b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/SolanaAccountsMappingService.kt index 5cf3903e..4d3c3ad0 100644 --- a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/SolanaAccountsMappingService.kt +++ b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/SolanaAccountsMappingService.kt @@ -5,12 +5,13 @@ import net.corda.core.node.AppServiceHub import net.corda.core.node.services.CordaService import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.solana.sdk.instruction.Pubkey +import java.util.UUID @CordaService class SolanaAccountsMappingService(appServiceHub: AppServiceHub) : SingletonSerializeAsToken() { var participants: Map - var mints: Map - var mintAuthorities: Map + var mints: Map + var mintAuthorities: Map // TODO quiet failover as this service is used in MockNetwork by nodes without a config for now // will be fail fast after separating flows to specific CordApps and adding Birding Authority which will have exclusive flows and this service @@ -26,14 +27,19 @@ class SolanaAccountsMappingService(appServiceHub: AppServiceHub) : SingletonSeri emptyMap() } mints = try { - (cfg.get("mints") as? Map)?.map { (k, v) -> k to Pubkey.fromBase58(v) }?.toMap() + (cfg.get("mints") as? Map)?.map { (k, v) -> UUID.fromString(k) to Pubkey.fromBase58(v) } + ?.toMap() ?: emptyMap() } catch (_: Exception) { emptyMap() } mintAuthorities = try { - (cfg.get("mintAuthorities") as? Map)?.map { (k, v) -> k to Pubkey.fromBase58(v) }?.toMap() + (cfg.get("mintAuthorities") as? Map)?.map { (k, v) -> + UUID.fromString(k) to Pubkey.fromBase58( + v + ) + }?.toMap() ?: emptyMap() } catch (_: Exception) { emptyMap() diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt index beb3a7e5..ede2849a 100644 --- a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt +++ b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt @@ -20,10 +20,10 @@ import com.r3.corda.lib.tokens.bridging.SolanaAccountsMappingService import com.r3.corda.lib.tokens.bridging.contracts.BridgingContract import com.r3.corda.lib.tokens.bridging.states.BridgedAssetLockState import com.r3.corda.lib.tokens.contracts.states.FungibleToken +import com.r3.corda.lib.tokens.contracts.types.TokenPointer import net.corda.core.contracts.StateAndRef import net.corda.core.node.services.Vault import net.corda.core.utilities.ProgressTracker -import net.corda.solana.sdk.instruction.Pubkey @InitiatingFlow @StartableByRPC @@ -38,7 +38,7 @@ class BridgeStock( @Suspendable override fun call(): String { - //TODO add list of StetRef to bridge in flow parameters + //TODO this simulates getting updates from vault val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.UNCONSUMED) val token: StateAndRef = serviceHub.vaultService .queryBy(FungibleToken::class.java, criteria) @@ -73,7 +73,7 @@ class BridgeFungibleTokens constructor( val holder: AbstractParty, val observers: List = emptyList(), - val token: StateAndRef, //TODO should be FungibleToken + val token: StateAndRef, //TODO should be FungibleToken, TODO change to any TokenType would need amendments to UUID retrieval below val bridgeAuthority: Party ) : FlowLogic() { @@ -85,11 +85,12 @@ constructor( val additionalOutput: ContractState = BridgedAssetLockState(listOf(ourIdentity)) - val symbol = "TEST" //TODO mapping should change to LinearId + val cordaTokenId = (token.state.data.amount.token.tokenType as TokenPointer<*>).pointer.pointer.id + val solanaAccountMapping = serviceHub.cordaService(SolanaAccountsMappingService::class.java) - val destination = solanaAccountMapping.participants[ourIdentity.name]!! //TODO handle null - val mint = solanaAccountMapping.mints[symbol]!! //TODO handle null - val mintAuthority = solanaAccountMapping.mintAuthorities[symbol]!! //TODO handle null + val destination = solanaAccountMapping.participants[ourIdentity.name]!! //TODO handle null, TODo eliminate this + val mint = solanaAccountMapping.mints[cordaTokenId]!! //TODO handle null + val mintAuthority = solanaAccountMapping.mintAuthorities[cordaTokenId]!! //TODO handle null val additionalCommand = BridgingContract.BridgingCommand.BridgeToSolana( destination, bridgeAuthority diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/net/corda/samples/stockpaydividend/flows/CreateAndIssueStock.kt b/Tokens/stockpaydividend/workflows/src/main/kotlin/net/corda/samples/stockpaydividend/flows/CreateAndIssueStock.kt index 251074ff..5245a288 100644 --- a/Tokens/stockpaydividend/workflows/src/main/kotlin/net/corda/samples/stockpaydividend/flows/CreateAndIssueStock.kt +++ b/Tokens/stockpaydividend/workflows/src/main/kotlin/net/corda/samples/stockpaydividend/flows/CreateAndIssueStock.kt @@ -28,7 +28,9 @@ class CreateAndIssueStock(val symbol: String, val currency: String, val price: BigDecimal, val issueVol: Int, - val notary: Party) : FlowLogic() { + val notary: Party, + val linearId: UniqueIdentifier = UniqueIdentifier() +) : FlowLogic() { override val progressTracker = ProgressTracker() @Suspendable @@ -39,10 +41,7 @@ class CreateAndIssueStock(val symbol: String, val observers: List = getObserverLegalIdentities(identityService) // Construct the output StockState - val stockState = StockState(ourIdentity, symbol, - name, currency, - price, - UniqueIdentifier()) + val stockState = StockState(ourIdentity, symbol, name, currency, price, linearId) // The notary provided here will be used in all future actions of this token val transactionState = stockState withNotary notary diff --git a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt index d2555e54..49099523 100644 --- a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt +++ b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt @@ -7,6 +7,7 @@ import com.r3.corda.lib.tokens.contracts.states.FungibleToken import com.r3.corda.lib.tokens.contracts.types.TokenPointer import com.r3.corda.lib.tokens.workflows.utilities.tokenBalance import net.corda.core.contracts.StateAndRef +import net.corda.core.contracts.UniqueIdentifier import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party @@ -63,7 +64,7 @@ class FlowTests { val STOCK_PRICE = BigDecimal.valueOf(7.4) val ISSUING_STOCK_QUANTITY = 2000 val BUYING_STOCK = java.lang.Long.valueOf(500) - + val LINEAR_ID = UniqueIdentifier() companion object { val notaryName = CordaX500Name("Solana Notary Service", "Zurich", "CH") @@ -132,8 +133,8 @@ class FlowTests { val companyCfg = mapOf( "participants" to mapOf(COMPANY.name.toString() to tokenAccount.base58()), - "mints" to mapOf(STOCK_SYMBOL to tokenMint.base58()), - "mintAuthorities" to mapOf(STOCK_SYMBOL to mintAuthority.account.base58()) + "mints" to mapOf(LINEAR_ID.toString() to tokenMint.base58()), + "mintAuthorities" to mapOf(LINEAR_ID.toString() to mintAuthority.account.base58()) ) company = network!!.createNode( MockNodeParameters( @@ -227,6 +228,7 @@ class FlowTests { //Retrieve states from receiver val receivedStockStatesPages = shareholder!!.services.vaultService.queryBy(StockState::class.java).states val receivedStockState = receivedStockStatesPages[0].state.data + val cordaTokenId = receivedStockState.toPointer().pointer.pointer.id val (quantity) = shareholder!!.services.vaultService.tokenBalance( receivedStockState.toPointer( receivedStockState.javaClass @@ -257,7 +259,8 @@ class FlowTests { STOCK_CURRENCY, STOCK_PRICE, ISSUING_STOCK_QUANTITY, - notaryParty!! + notaryParty!!, + LINEAR_ID ) ) network!!.runNetwork() From 865c4c44cd25f8df8e52214972581c8ad3d2612b Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Mon, 29 Sep 2025 15:14:29 +0100 Subject: [PATCH 18/27] Added second token type to test and modify query inside flow to select by symbol --- .../lib/tokens/bridging/rpc/BridgeTokens.kt | 13 +++-- .../samples/stockpaydividend/FlowTests.kt | 55 +++++++++++++++---- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt index ede2849a..d0e667aa 100644 --- a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt +++ b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt @@ -13,7 +13,6 @@ import net.corda.core.flows.StartableByRPC import net.corda.core.flows.StartableByService import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party -import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.transactions.SignedTransaction import com.r3.corda.lib.tokens.bridging.BridgeFungibleTokensFlow import com.r3.corda.lib.tokens.bridging.SolanaAccountsMappingService @@ -22,8 +21,9 @@ import com.r3.corda.lib.tokens.bridging.states.BridgedAssetLockState import com.r3.corda.lib.tokens.contracts.states.FungibleToken import com.r3.corda.lib.tokens.contracts.types.TokenPointer import net.corda.core.contracts.StateAndRef -import net.corda.core.node.services.Vault import net.corda.core.utilities.ProgressTracker +import net.corda.samples.stockpaydividend.states.StockState +import com.r3.corda.lib.tokens.workflows.utilities.tokenAmountsByToken @InitiatingFlow @StartableByRPC @@ -39,10 +39,11 @@ class BridgeStock( override fun call(): String { //TODO this simulates getting updates from vault - val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.UNCONSUMED) - val token: StateAndRef = serviceHub.vaultService - .queryBy(FungibleToken::class.java, criteria) - .states.first() + val page = + serviceHub.vaultService.queryBy(StockState::class.java) //TODO + UNCONSUMED query and belonging to our identity + val states = page.states.filter { it.state.data.symbol == symbol } + val pointer: TokenPointer = states.map { it.state.data.toPointer(StockState::class.java) }.first() + val token: StateAndRef = serviceHub.vaultService.tokenAmountsByToken(pointer).states.first() //Use built-in flow for move tokens to the recipient val stx = subFlow( diff --git a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt index 49099523..7a62845d 100644 --- a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt +++ b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt @@ -65,6 +65,9 @@ class FlowTests { val ISSUING_STOCK_QUANTITY = 2000 val BUYING_STOCK = java.lang.Long.valueOf(500) val LINEAR_ID = UniqueIdentifier() + val STOCK_SYMBOL_2 = "TEST2" + val LINEAR_ID_2 = UniqueIdentifier() + val STOCK_NAME_2 = "Test Stock 2" companion object { val notaryName = CordaX500Name("Solana Notary Service", "Zurich", "CH") @@ -251,7 +254,7 @@ class FlowTests { @Throws(ExecutionException::class, InterruptedException::class) fun bridgeTest() { - // Issue Stock + // Issue 1st Stock var future = company!!.startFlow( CreateAndIssueStock( STOCK_SYMBOL, @@ -266,10 +269,23 @@ class FlowTests { network!!.runNetwork() future.get() - val stockStatesPages = company!!.services.vaultService.queryBy(StockState::class.java).states - Assert.assertNotNull(stockStatesPages[0]) - val stockState = stockStatesPages[0].state.data - val stockStatePointer = stockState.toPointer(stockState.javaClass) + // Issue 2nd Stock + future = company!!.startFlow( + CreateAndIssueStock( + STOCK_SYMBOL_2, + STOCK_NAME_2, + STOCK_CURRENCY, + STOCK_PRICE, + ISSUING_STOCK_QUANTITY, + notaryParty!!, + LINEAR_ID_2 + ) + ) + network!!.runNetwork() + future.get() + + // First stock to be bridged + var stockStatePointer = getTokensPointers(company!!, STOCK_SYMBOL).first() val (startCordaQuantity) = company!!.services.vaultService.tokenBalance(stockStatePointer) Assert.assertEquals(ISSUING_STOCK_QUANTITY.toLong(), startCordaQuantity) @@ -278,6 +294,11 @@ class FlowTests { .checkResponse("getTokenAccountBalance") Assert.assertEquals("0", startSolanaBalance!!.amount) + // Second stock - to verify it remains unaffected + var stock2StatePointer = getTokensPointers(company!!, STOCK_SYMBOL_2).first() + var (start2CordaQuantity) = company!!.services.vaultService.tokenBalance(stock2StatePointer) + Assert.assertEquals(ISSUING_STOCK_QUANTITY.toLong(), start2CordaQuantity) + // TODO Spend all to avoid having a change to yourself - then can't distinguish which amount si to mint which is a change future = company!!.startFlow( BridgeStock( @@ -290,12 +311,8 @@ class FlowTests { network!!.runNetwork() future.get() - val remainingStockStatesPages = company!!.services.vaultService.queryBy(StockState::class.java).states - Assert.assertNotNull(remainingStockStatesPages[0]) - val remainingStockState = remainingStockStatesPages[0].state.data - val remainingStockStatePointer: TokenPointer = - remainingStockState.toPointer(remainingStockState.javaClass) - val (finalCordaQuantity) = company!!.services.vaultService.tokenBalance(remainingStockStatePointer) + stockStatePointer = getTokensPointers(company!!, STOCK_SYMBOL).first() + val (finalCordaQuantity) = company!!.services.vaultService.tokenBalance(stockStatePointer) Assert.assertEquals( ISSUING_STOCK_QUANTITY.toLong(), finalCordaQuantity @@ -303,7 +320,7 @@ class FlowTests { val token: StateAndRef? = company!!.services.vaultService.queryBy(FungibleToken::class.java).states.firstOrNull { - it.state.data.amount.token.tokenType == remainingStockStatePointer + it.state.data.amount.token.tokenType == stockStatePointer } Assert.assertNotNull(token) val bridgingState: StateAndRef? = @@ -320,6 +337,20 @@ class FlowTests { .checkResponse("getTokenAccountBalance") Assert.assertEquals(ISSUING_STOCK_QUANTITY.toString(), finalSolanaBalance!!.amount) + + // Second stock balance remains unchanged /unaffected + stock2StatePointer = getTokensPointers(company!!, STOCK_SYMBOL_2).first() + start2CordaQuantity = company!!.services.vaultService.tokenBalance(stock2StatePointer).quantity + Assert.assertEquals(ISSUING_STOCK_QUANTITY.toLong(), start2CordaQuantity) + } + + private fun getTokensPointers(node: StartedMockNode, symbol: String): List> { + val page = + node.services.vaultService.queryBy(StockState::class.java) //TODO + UNCONSUMED query and belonging to our identity + val states = page.states.filter { it.state.data.symbol == symbol } + val pointers: List> = states.map { it.state.data.toPointer(StockState::class.java) } + return pointers + } } \ No newline at end of file From e8a3b2ddeb39beab3a4d8446693649c74c9e5b7d Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Mon, 29 Sep 2025 15:29:01 +0100 Subject: [PATCH 19/27] Test util method refactoring --- .../corda/samples/stockpaydividend/FlowTests.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt index 7a62845d..c7006f88 100644 --- a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt +++ b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt @@ -285,7 +285,7 @@ class FlowTests { future.get() // First stock to be bridged - var stockStatePointer = getTokensPointers(company!!, STOCK_SYMBOL).first() + var stockStatePointer = getTokensPointer(company!!, STOCK_SYMBOL) val (startCordaQuantity) = company!!.services.vaultService.tokenBalance(stockStatePointer) Assert.assertEquals(ISSUING_STOCK_QUANTITY.toLong(), startCordaQuantity) @@ -295,7 +295,7 @@ class FlowTests { Assert.assertEquals("0", startSolanaBalance!!.amount) // Second stock - to verify it remains unaffected - var stock2StatePointer = getTokensPointers(company!!, STOCK_SYMBOL_2).first() + var stock2StatePointer = getTokensPointer(company!!, STOCK_SYMBOL_2) var (start2CordaQuantity) = company!!.services.vaultService.tokenBalance(stock2StatePointer) Assert.assertEquals(ISSUING_STOCK_QUANTITY.toLong(), start2CordaQuantity) @@ -311,7 +311,7 @@ class FlowTests { network!!.runNetwork() future.get() - stockStatePointer = getTokensPointers(company!!, STOCK_SYMBOL).first() + stockStatePointer = getTokensPointer(company!!, STOCK_SYMBOL) val (finalCordaQuantity) = company!!.services.vaultService.tokenBalance(stockStatePointer) Assert.assertEquals( ISSUING_STOCK_QUANTITY.toLong(), @@ -339,18 +339,19 @@ class FlowTests { Assert.assertEquals(ISSUING_STOCK_QUANTITY.toString(), finalSolanaBalance!!.amount) // Second stock balance remains unchanged /unaffected - stock2StatePointer = getTokensPointers(company!!, STOCK_SYMBOL_2).first() + stock2StatePointer = getTokensPointer(company!!, STOCK_SYMBOL_2) start2CordaQuantity = company!!.services.vaultService.tokenBalance(stock2StatePointer).quantity Assert.assertEquals(ISSUING_STOCK_QUANTITY.toLong(), start2CordaQuantity) } - private fun getTokensPointers(node: StartedMockNode, symbol: String): List> { + private fun getTokensPointer(node: StartedMockNode, symbol: String): TokenPointer { val page = - node.services.vaultService.queryBy(StockState::class.java) //TODO + UNCONSUMED query and belonging to our identity + node.services.vaultService.queryBy(StockState::class.java) //TODO + UNCONSUMED query and belonging to our identity val states = page.states.filter { it.state.data.symbol == symbol } val pointers: List> = states.map { it.state.data.toPointer(StockState::class.java) } - return pointers + Assert.assertEquals(1, pointers.size) + return pointers.first() } } \ No newline at end of file From 8edc2da4db805e647f2d34eda9f5470bb6774f62 Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Mon, 29 Sep 2025 15:46:08 +0100 Subject: [PATCH 20/27] Tidy up --- .../com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt index d0e667aa..0b839a5a 100644 --- a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt +++ b/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt @@ -1,7 +1,6 @@ package com.r3.corda.lib.tokens.bridging.rpc import co.paralleluniverse.fibers.Suspendable -import com.r3.corda.lib.tokens.contracts.types.TokenType import com.r3.corda.lib.tokens.workflows.flows.move.MoveTokensFlowHandler import com.r3.corda.lib.tokens.workflows.utilities.sessionsForParties import net.corda.core.contracts.ContractState @@ -61,15 +60,13 @@ class BridgeStock( } /** - * Initiating flow used to bridge amounts of tokens of the same party. - * - * Call this for one [TokenType] at a time. + * Initiating flow used to bridge token of the same party. * * @param observers optional observing parties to which the transaction will be broadcast */ @StartableByService @InitiatingFlow -class BridgeFungibleTokens +class BridgeFungibleTokens //TODO move away from RPC package @JvmOverloads constructor( val holder: AbstractParty, From 6495c56a70a325541487c1974e93b1249d04ec00 Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Mon, 29 Sep 2025 16:16:59 +0100 Subject: [PATCH 21/27] Added Bridging Authority to the test, WIP to get the moved token to bridged instead of a first token within BA --- .../samples/stockpaydividend/FlowTests.kt | 69 ++++++++++++------- 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt index c7006f88..59cade89 100644 --- a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt +++ b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt @@ -47,6 +47,7 @@ class FlowTests { private var observer: StartedMockNode? = null private var shareholder: StartedMockNode? = null private var bank: StartedMockNode? = null + private var ba: StartedMockNode? = null private var exDate: Date? = null private var payDate: Date? = null @@ -57,6 +58,7 @@ class FlowTests { var SHAREHOLDER = TestIdentity(CordaX500Name("Shareholder", "TestVillage", "US")) var BANK = TestIdentity(CordaX500Name("Bank", "Rulerland", "US")) var OBSERVER = TestIdentity(CordaX500Name("Observer", "Rulerland", "US")) + var BA = TestIdentity(CordaX500Name("Bridging Authority", "New York", "US")) val STOCK_SYMBOL = "TEST" val STOCK_NAME = "Test Stock" @@ -120,12 +122,13 @@ class FlowTests { @Before fun setup() { - val app = TestCordapp.findCordapp("net.corda.samples.stockpaydividend.flows") + val bridgingCordapp = + TestCordapp.findCordapp("net.corda.samples.stockpaydividend.flows") //TODO when the Cordapp will be moved to platform, this will be exclusively only for BA network = MockNetwork( MockNetworkParameters( cordappsForAllNodes = listOf( TestCordapp.findCordapp("net.corda.samples.stockpaydividend.contracts"), - app, + bridgingCordapp, TestCordapp.findCordapp("com.r3.corda.lib.tokens.contracts"), TestCordapp.findCordapp("com.r3.corda.lib.tokens.workflows"), DUMMY_CONTRACTS_CORDAPP @@ -135,21 +138,22 @@ class FlowTests { ) val companyCfg = mapOf( - "participants" to mapOf(COMPANY.name.toString() to tokenAccount.base58()), + "participants" to mapOf(BA.name.toString() to tokenAccount.base58()), //TODO introduction of BA shows that participants are not needed - it's rather a single BA account "mints" to mapOf(LINEAR_ID.toString() to tokenMint.base58()), "mintAuthorities" to mapOf(LINEAR_ID.toString() to mintAuthority.account.base58()) ) - company = network!!.createNode( - MockNodeParameters( - legalName = COMPANY.name, - additionalCordapps = listOf(app.withConfig(companyCfg)) - ) - ) + company = network!!.createPartyNode(COMPANY.name) observer = network!!.createPartyNode(OBSERVER.name) shareholder = network!!.createPartyNode(SHAREHOLDER.name) bank = network!!.createPartyNode(BANK.name) notary = network!!.notaryNodes[0] notaryParty = notary!!.info.legalIdentities[0] + ba = network!!.createNode( + MockNodeParameters( + legalName = BA.name, + additionalCordapps = listOf(bridgingCordapp.withConfig(companyCfg)) + ) + ) // Set execution date as tomorrow val c = Calendar.getInstance() @@ -254,7 +258,7 @@ class FlowTests { @Throws(ExecutionException::class, InterruptedException::class) fun bridgeTest() { - // Issue 1st Stock + // Issue 1st Stock on Company node var future = company!!.startFlow( CreateAndIssueStock( STOCK_SYMBOL, @@ -269,8 +273,8 @@ class FlowTests { network!!.runNetwork() future.get() - // Issue 2nd Stock - future = company!!.startFlow( + // Issue 2nd Stock on Bridge Authority node to verify it remains unaffected + future = ba!!.startFlow( CreateAndIssueStock( STOCK_SYMBOL_2, STOCK_NAME_2, @@ -284,7 +288,7 @@ class FlowTests { network!!.runNetwork() future.get() - // First stock to be bridged + // First stock to be bridged - moving from Company to Bridge Authority var stockStatePointer = getTokensPointer(company!!, STOCK_SYMBOL) val (startCordaQuantity) = company!!.services.vaultService.tokenBalance(stockStatePointer) Assert.assertEquals(ISSUING_STOCK_QUANTITY.toLong(), startCordaQuantity) @@ -294,37 +298,51 @@ class FlowTests { .checkResponse("getTokenAccountBalance") Assert.assertEquals("0", startSolanaBalance!!.amount) - // Second stock - to verify it remains unaffected - var stock2StatePointer = getTokensPointer(company!!, STOCK_SYMBOL_2) - var (start2CordaQuantity) = company!!.services.vaultService.tokenBalance(stock2StatePointer) + // Second stock on Bridging Authority - to verify it remains unaffected + var stock2StatePointer = getTokensPointer(ba!!, STOCK_SYMBOL_2) + var (start2CordaQuantity) = ba!!.services.vaultService.tokenBalance(stock2StatePointer) Assert.assertEquals(ISSUING_STOCK_QUANTITY.toLong(), start2CordaQuantity) - // TODO Spend all to avoid having a change to yourself - then can't distinguish which amount si to mint which is a change - future = company!!.startFlow( + // Move Stock + future = + company!!.startFlow(MoveStock(STOCK_SYMBOL, ISSUING_STOCK_QUANTITY.toLong(), ba!!.info.legalIdentities[0])) + network!!.runNetwork() + future.get() + + // Company has no longer the amount of stocks + val (endCordaQuantity) = company!!.services.vaultService.tokenBalance(stockStatePointer) + Assert.assertEquals(0L, endCordaQuantity) + + // Bridging Authority received the amount of stocks + stockStatePointer = getTokensPointer(ba!!, STOCK_SYMBOL) + val (startBridgingAuthorityCordaQuantity) = ba!!.services.vaultService.tokenBalance(stockStatePointer) + Assert.assertEquals(ISSUING_STOCK_QUANTITY.toLong(), startBridgingAuthorityCordaQuantity) + + future = ba!!.startFlow( BridgeStock( STOCK_SYMBOL, startCordaQuantity , - company!!.info.legalIdentities[0] + ba!!.info.legalIdentities[0] //TODO remove this as will be internally moved to own CI ) ) network!!.runNetwork() future.get() - stockStatePointer = getTokensPointer(company!!, STOCK_SYMBOL) - val (finalCordaQuantity) = company!!.services.vaultService.tokenBalance(stockStatePointer) + stockStatePointer = getTokensPointer(ba!!, STOCK_SYMBOL) + val (finalCordaQuantity) = ba!!.services.vaultService.tokenBalance(stockStatePointer) Assert.assertEquals( ISSUING_STOCK_QUANTITY.toLong(), finalCordaQuantity ) // TODO this is Corda move token to self, so it still the same amount as at the beginning val token: StateAndRef? = - company!!.services.vaultService.queryBy(FungibleToken::class.java).states.firstOrNull { + ba!!.services.vaultService.queryBy(FungibleToken::class.java).states.firstOrNull { it.state.data.amount.token.tokenType == stockStatePointer } Assert.assertNotNull(token) val bridgingState: StateAndRef? = - company!!.services.vaultService.queryBy(BridgedAssetLockState::class.java).states.firstOrNull() + ba!!.services.vaultService.queryBy(BridgedAssetLockState::class.java).states.firstOrNull() Assert.assertNotNull(bridgingState) Assert.assertTrue( @@ -339,13 +357,12 @@ class FlowTests { Assert.assertEquals(ISSUING_STOCK_QUANTITY.toString(), finalSolanaBalance!!.amount) // Second stock balance remains unchanged /unaffected - stock2StatePointer = getTokensPointer(company!!, STOCK_SYMBOL_2) - start2CordaQuantity = company!!.services.vaultService.tokenBalance(stock2StatePointer).quantity + stock2StatePointer = getTokensPointer(ba!!, STOCK_SYMBOL_2) + start2CordaQuantity = ba!!.services.vaultService.tokenBalance(stock2StatePointer).quantity Assert.assertEquals(ISSUING_STOCK_QUANTITY.toLong(), start2CordaQuantity) } - private fun getTokensPointer(node: StartedMockNode, symbol: String): TokenPointer { val page = node.services.vaultService.queryBy(StockState::class.java) //TODO + UNCONSUMED query and belonging to our identity From ff11d9c2867b5616910c40118fedd9a30af9791e Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Tue, 30 Sep 2025 12:55:51 +0100 Subject: [PATCH 22/27] Refactor SDk to own module and cordapps --- .../bridging-contracts/build.gradle | 57 ++++++++++++++ .../bridging/contracts/BridgingContract.kt | 0 .../bridging/states/BridgedAssetLockState.kt | 0 .../bridging-flows/build.gradle | 68 ++++++++++++++++ .../flows}/BridgeFungibleTokensFlow.kt | 2 +- .../bridging/flows}/BridgeTokensUtilities.kt | 2 +- .../flows}/SolanaAccountsMappingService.kt | 14 ++-- .../bridging/flows}/rpc/BridgeTokens.kt | 20 ++--- Tokens/stockpaydividend/settings.gradle | 3 +- .../stockpaydividend/workflows/build.gradle | 2 + .../stockpaydividend/flows/QueryFlows.kt | 26 ++++++- .../samples/stockpaydividend/FlowTests.kt | 77 +++++++++++-------- 12 files changed, 212 insertions(+), 59 deletions(-) create mode 100644 Tokens/stockpaydividend/bridging-contracts/build.gradle rename Tokens/stockpaydividend/{contracts => bridging-contracts}/src/main/kotlin/com/r3/corda/lib/tokens/bridging/contracts/BridgingContract.kt (100%) rename Tokens/stockpaydividend/{contracts => bridging-contracts}/src/main/kotlin/com/r3/corda/lib/tokens/bridging/states/BridgedAssetLockState.kt (100%) create mode 100644 Tokens/stockpaydividend/bridging-flows/build.gradle rename Tokens/stockpaydividend/{workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging => bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows}/BridgeFungibleTokensFlow.kt (97%) rename Tokens/stockpaydividend/{workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging => bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows}/BridgeTokensUtilities.kt (98%) rename Tokens/stockpaydividend/{workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging => bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows}/SolanaAccountsMappingService.kt (75%) rename Tokens/stockpaydividend/{workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging => bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows}/rpc/BridgeTokens.kt (80%) diff --git a/Tokens/stockpaydividend/bridging-contracts/build.gradle b/Tokens/stockpaydividend/bridging-contracts/build.gradle new file mode 100644 index 00000000..1bb80885 --- /dev/null +++ b/Tokens/stockpaydividend/bridging-contracts/build.gradle @@ -0,0 +1,57 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version.toInteger() + minimumPlatformVersion corda_platform_version.toInteger() + workflow { + name "Bridging Contracts" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") + } + } + test { + resources { + srcDir rootProject.file("config/test") + } + } + integrationTest { + kotlin { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integrationTest/kotlin') + } + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + + // Corda dependencies. + cordaProvided "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaProvided "$corda_release_group:corda:$corda_release_version" + + // Token SDK dependencies. + cordaProvided "$tokens_release_group:tokens-contracts:$tokens_release_version" + cordaProvided "$tokens_release_group:tokens-workflows:$tokens_release_version" + + cordaProvided "$corda_release_group:corda-solana-sdk:$corda_release_version" + cordaProvided "$corda_release_group:corda-solana-common:$corda_release_version" +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} + +test { + jvmArgs = rootProject.ext.testJvmArgs +} \ No newline at end of file diff --git a/Tokens/stockpaydividend/contracts/src/main/kotlin/com/r3/corda/lib/tokens/bridging/contracts/BridgingContract.kt b/Tokens/stockpaydividend/bridging-contracts/src/main/kotlin/com/r3/corda/lib/tokens/bridging/contracts/BridgingContract.kt similarity index 100% rename from Tokens/stockpaydividend/contracts/src/main/kotlin/com/r3/corda/lib/tokens/bridging/contracts/BridgingContract.kt rename to Tokens/stockpaydividend/bridging-contracts/src/main/kotlin/com/r3/corda/lib/tokens/bridging/contracts/BridgingContract.kt diff --git a/Tokens/stockpaydividend/contracts/src/main/kotlin/com/r3/corda/lib/tokens/bridging/states/BridgedAssetLockState.kt b/Tokens/stockpaydividend/bridging-contracts/src/main/kotlin/com/r3/corda/lib/tokens/bridging/states/BridgedAssetLockState.kt similarity index 100% rename from Tokens/stockpaydividend/contracts/src/main/kotlin/com/r3/corda/lib/tokens/bridging/states/BridgedAssetLockState.kt rename to Tokens/stockpaydividend/bridging-contracts/src/main/kotlin/com/r3/corda/lib/tokens/bridging/states/BridgedAssetLockState.kt diff --git a/Tokens/stockpaydividend/bridging-flows/build.gradle b/Tokens/stockpaydividend/bridging-flows/build.gradle new file mode 100644 index 00000000..64baa267 --- /dev/null +++ b/Tokens/stockpaydividend/bridging-flows/build.gradle @@ -0,0 +1,68 @@ +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.quasar-utils' + +cordapp { + targetPlatformVersion corda_platform_version.toInteger() + minimumPlatformVersion corda_platform_version.toInteger() + workflow { + name "Token Bridging Flows" + vendor "Corda Open Source" + licence "Apache License, Version 2.0" + versionId 1 + } +} + +sourceSets { + main { + resources { + srcDir rootProject.file("config/dev") + } + } + test { + resources { + srcDir rootProject.file("config/test") + } + } + integrationTest { + kotlin { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integrationTest/kotlin') + } + } +} + + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" + testImplementation "junit:junit:$junit_version" + + // Corda dependencies. + cordaProvided "$corda_core_release_group:corda-core:$corda_core_release_version" + cordaProvided "$corda_release_group:corda:$corda_release_version" + testImplementation "$corda_release_group:corda-node-driver:$corda_release_version" + testImplementation "$corda_core_release_group:corda-core-test-utils:$corda_core_release_version" + + testImplementation "$corda_release_group:corda-solana-common:$corda_release_version" + testImplementation "$corda_release_group:corda-test-utils:$corda_release_version" + + // CorDapp dependencies. + cordapp project(":bridging-contracts") + + // Token SDK dependencies. + cordaProvided "$tokens_release_group:tokens-contracts:$tokens_release_version" + cordaProvided "$tokens_release_group:tokens-workflows:$tokens_release_version" + + cordaProvided "$corda_release_group:corda-solana-sdk:$corda_release_version" + cordaProvided "$corda_release_group:corda-solana-common:$corda_release_version" +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} + +test { + jvmArgs = rootProject.ext.testJvmArgs +} \ No newline at end of file diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeFungibleTokensFlow.kt b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeFungibleTokensFlow.kt similarity index 97% rename from Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeFungibleTokensFlow.kt rename to Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeFungibleTokensFlow.kt index 84c93fcd..6717f1cb 100644 --- a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeFungibleTokensFlow.kt +++ b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeFungibleTokensFlow.kt @@ -1,4 +1,4 @@ -package com.r3.corda.lib.tokens.bridging +package com.r3.corda.lib.tokens.bridging.flows import co.paralleluniverse.fibers.Suspendable import com.r3.corda.lib.tokens.bridging.contracts.BridgingContract diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeTokensUtilities.kt b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeTokensUtilities.kt similarity index 98% rename from Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeTokensUtilities.kt rename to Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeTokensUtilities.kt index 6a01e4f8..c3ae2674 100644 --- a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/BridgeTokensUtilities.kt +++ b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeTokensUtilities.kt @@ -1,4 +1,4 @@ -package com.r3.corda.lib.tokens.bridging +package com.r3.corda.lib.tokens.bridging.flows import co.paralleluniverse.fibers.Suspendable import com.r3.corda.lib.tokens.bridging.contracts.BridgingContract diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/SolanaAccountsMappingService.kt b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/SolanaAccountsMappingService.kt similarity index 75% rename from Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/SolanaAccountsMappingService.kt rename to Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/SolanaAccountsMappingService.kt index 4d3c3ad0..0bfaebd9 100644 --- a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/SolanaAccountsMappingService.kt +++ b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/SolanaAccountsMappingService.kt @@ -1,4 +1,4 @@ -package com.r3.corda.lib.tokens.bridging +package com.r3.corda.lib.tokens.bridging.flows import net.corda.core.identity.CordaX500Name import net.corda.core.node.AppServiceHub @@ -13,8 +13,6 @@ class SolanaAccountsMappingService(appServiceHub: AppServiceHub) : SingletonSeri var mints: Map var mintAuthorities: Map - // TODO quiet failover as this service is used in MockNetwork by nodes without a config for now - // will be fail fast after separating flows to specific CordApps and adding Birding Authority which will have exclusive flows and this service init { val cfg = appServiceHub.getAppContext().config participants = try { @@ -22,27 +20,27 @@ class SolanaAccountsMappingService(appServiceHub: AppServiceHub) : SingletonSeri CordaX500Name.parse(k) to Pubkey.fromBase58( v ) - }?.toMap() ?: emptyMap() + }?.toMap() + ?: emptyMap() // throw IllegalArgumentException("participants config missing for SolanaAccountsMappingService") } catch (_: Exception) { emptyMap() } mints = try { (cfg.get("mints") as? Map)?.map { (k, v) -> UUID.fromString(k) to Pubkey.fromBase58(v) } ?.toMap() - ?: emptyMap() + ?: emptyMap() //throw IllegalArgumentException("mints config missing for SolanaAccountsMappingService") } catch (_: Exception) { emptyMap() } - mintAuthorities = try { (cfg.get("mintAuthorities") as? Map)?.map { (k, v) -> UUID.fromString(k) to Pubkey.fromBase58( v ) }?.toMap() - ?: emptyMap() + ?: emptyMap() //throw IllegalArgumentException("mintAuthorities config missing for SolanaAccountsMappingService") } catch (_: Exception) { emptyMap() } } -} \ No newline at end of file +} diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/rpc/BridgeTokens.kt similarity index 80% rename from Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt rename to Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/rpc/BridgeTokens.kt index 0b839a5a..04cfec9a 100644 --- a/Tokens/stockpaydividend/workflows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/rpc/BridgeTokens.kt +++ b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/rpc/BridgeTokens.kt @@ -1,4 +1,4 @@ -package com.r3.corda.lib.tokens.bridging.rpc +package com.r3.corda.lib.tokens.bridging.flows.rpc import co.paralleluniverse.fibers.Suspendable import com.r3.corda.lib.tokens.workflows.flows.move.MoveTokensFlowHandler @@ -13,22 +13,19 @@ import net.corda.core.flows.StartableByService import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.transactions.SignedTransaction -import com.r3.corda.lib.tokens.bridging.BridgeFungibleTokensFlow -import com.r3.corda.lib.tokens.bridging.SolanaAccountsMappingService +import com.r3.corda.lib.tokens.bridging.flows.BridgeFungibleTokensFlow +import com.r3.corda.lib.tokens.bridging.flows.SolanaAccountsMappingService import com.r3.corda.lib.tokens.bridging.contracts.BridgingContract import com.r3.corda.lib.tokens.bridging.states.BridgedAssetLockState import com.r3.corda.lib.tokens.contracts.states.FungibleToken import com.r3.corda.lib.tokens.contracts.types.TokenPointer import net.corda.core.contracts.StateAndRef import net.corda.core.utilities.ProgressTracker -import net.corda.samples.stockpaydividend.states.StockState -import com.r3.corda.lib.tokens.workflows.utilities.tokenAmountsByToken @InitiatingFlow @StartableByRPC class BridgeStock( - val symbol: String, - val quantity: Long, + val token: StateAndRef, //TODO change to a list val bridgeAuthority: Party ) : FlowLogic() { @@ -37,13 +34,6 @@ class BridgeStock( @Suspendable override fun call(): String { - //TODO this simulates getting updates from vault - val page = - serviceHub.vaultService.queryBy(StockState::class.java) //TODO + UNCONSUMED query and belonging to our identity - val states = page.states.filter { it.state.data.symbol == symbol } - val pointer: TokenPointer = states.map { it.state.data.toPointer(StockState::class.java) }.first() - val token: StateAndRef = serviceHub.vaultService.tokenAmountsByToken(pointer).states.first() - //Use built-in flow for move tokens to the recipient val stx = subFlow( BridgeFungibleTokens( @@ -54,7 +44,7 @@ class BridgeStock( ) ) - return ("\nBridged " + quantity + " " + symbol + " stocks to " + return ("\nBridged quantity " + token.state.data.amount + " " + " stocks to " + ourIdentity.name.organisation + ".\nTransaction ID: " + stx.id) } } diff --git a/Tokens/stockpaydividend/settings.gradle b/Tokens/stockpaydividend/settings.gradle index 2514aca2..853ff054 100644 --- a/Tokens/stockpaydividend/settings.gradle +++ b/Tokens/stockpaydividend/settings.gradle @@ -1,3 +1,4 @@ include 'workflows' include 'contracts' -include 'clients' \ No newline at end of file +include 'clients' +include 'bridging-contracts', 'bridging-flows' \ No newline at end of file diff --git a/Tokens/stockpaydividend/workflows/build.gradle b/Tokens/stockpaydividend/workflows/build.gradle index 432d0ea0..18aeb362 100644 --- a/Tokens/stockpaydividend/workflows/build.gradle +++ b/Tokens/stockpaydividend/workflows/build.gradle @@ -53,6 +53,8 @@ dependencies { // CorDapp dependencies. cordapp project(":contracts") + cordapp project(":bridging-contracts") + cordapp project(":bridging-flows") // Token SDK dependencies. cordaProvided "$tokens_release_group:tokens-contracts:$tokens_release_version" diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/net/corda/samples/stockpaydividend/flows/QueryFlows.kt b/Tokens/stockpaydividend/workflows/src/main/kotlin/net/corda/samples/stockpaydividend/flows/QueryFlows.kt index a014b368..9ad04536 100644 --- a/Tokens/stockpaydividend/workflows/src/main/kotlin/net/corda/samples/stockpaydividend/flows/QueryFlows.kt +++ b/Tokens/stockpaydividend/workflows/src/main/kotlin/net/corda/samples/stockpaydividend/flows/QueryFlows.kt @@ -1,11 +1,12 @@ package net.corda.samples.stockpaydividend.flows import co.paralleluniverse.fibers.Suspendable +import com.r3.corda.lib.tokens.contracts.states.FungibleToken import com.r3.corda.lib.tokens.contracts.types.TokenPointer -import com.r3.corda.lib.tokens.contracts.types.TokenType import com.r3.corda.lib.tokens.money.FiatCurrency.Companion.getInstance +import com.r3.corda.lib.tokens.workflows.utilities.tokenAmountsByToken import com.r3.corda.lib.tokens.workflows.utilities.tokenBalance -import net.corda.core.contracts.Amount +import net.corda.core.contracts.StateAndRef import net.corda.core.flows.FlowException import net.corda.core.flows.FlowLogic import net.corda.core.flows.InitiatingFlow @@ -43,4 +44,25 @@ class GetFiatBalance(private val currencyCode: String) : FlowLogic() { return "You currently have ${amount.quantity/100} ${amount.token.tokenIdentifier}" } +} + + +//TODO this is temporally +@InitiatingFlow +@StartableByRPC +class GetTokenToBridge( + val symbol: String +) : FlowLogic>>() { + + override val progressTracker = ProgressTracker() + + @Suspendable + override fun call(): List> { + val page = + serviceHub.vaultService.queryBy(StockState::class.java) //TODO + UNCONSUMED query and belonging to our identity + val states = page.states.filter { it.state.data.symbol == symbol } + val pointer: TokenPointer = states.map { it.state.data.toPointer(StockState::class.java) }.first() + val tokens: List> = serviceHub.vaultService.tokenAmountsByToken(pointer).states + return tokens + } } \ No newline at end of file diff --git a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt index 59cade89..46c6ee3e 100644 --- a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt +++ b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt @@ -1,8 +1,8 @@ package net.corda.samples.stockpaydividend import com.lmax.solana4j.api.PublicKey -import com.r3.corda.lib.tokens.bridging.rpc.BridgeStock import com.r3.corda.lib.tokens.bridging.states.BridgedAssetLockState +import com.r3.corda.lib.tokens.bridging.flows.rpc.BridgeStock import com.r3.corda.lib.tokens.contracts.states.FungibleToken import com.r3.corda.lib.tokens.contracts.types.TokenPointer import com.r3.corda.lib.tokens.workflows.utilities.tokenBalance @@ -35,7 +35,6 @@ import java.util.* import java.util.concurrent.ExecutionException import net.corda.testing.node.MockNetworkNotarySpec import net.corda.testing.node.MockNodeParameters -import net.corda.testing.node.internal.DUMMY_CONTRACTS_CORDAPP import net.corda.testing.solana.randomKeypairFile import org.junit.ClassRule import org.junit.rules.TemporaryFolder @@ -47,7 +46,7 @@ class FlowTests { private var observer: StartedMockNode? = null private var shareholder: StartedMockNode? = null private var bank: StartedMockNode? = null - private var ba: StartedMockNode? = null + private var bridgingAuthority: StartedMockNode? = null private var exDate: Date? = null private var payDate: Date? = null @@ -122,36 +121,35 @@ class FlowTests { @Before fun setup() { - val bridgingCordapp = - TestCordapp.findCordapp("net.corda.samples.stockpaydividend.flows") //TODO when the Cordapp will be moved to platform, this will be exclusively only for BA + val bridgingContractsCordapp = TestCordapp.findCordapp("com.r3.corda.lib.tokens.bridging.contracts") + val bridgingFlowsCordapp = TestCordapp.findCordapp("com.r3.corda.lib.tokens.bridging.flows") + val baConfig = mapOf( + "participants" to mapOf(BA.name.toString() to tokenAccount.base58()), //TODO introduction of BA shows that participants are not needed - it's rather a single BA account + "mints" to mapOf(LINEAR_ID.toString() to tokenMint.base58()), + "mintAuthorities" to mapOf(LINEAR_ID.toString() to mintAuthority.account.base58()) + ) network = MockNetwork( MockNetworkParameters( cordappsForAllNodes = listOf( TestCordapp.findCordapp("net.corda.samples.stockpaydividend.contracts"), - bridgingCordapp, + TestCordapp.findCordapp("net.corda.samples.stockpaydividend.flows"), TestCordapp.findCordapp("com.r3.corda.lib.tokens.contracts"), - TestCordapp.findCordapp("com.r3.corda.lib.tokens.workflows"), - DUMMY_CONTRACTS_CORDAPP + TestCordapp.findCordapp("com.r3.corda.lib.tokens.workflows") ), notarySpecs = listOf(MockNetworkNotarySpec(notaryName, notaryConfig = createNotaryConfig())), networkParameters = testNetworkParameters(minimumPlatformVersion = 4) ) ) - val companyCfg = mapOf( - "participants" to mapOf(BA.name.toString() to tokenAccount.base58()), //TODO introduction of BA shows that participants are not needed - it's rather a single BA account - "mints" to mapOf(LINEAR_ID.toString() to tokenMint.base58()), - "mintAuthorities" to mapOf(LINEAR_ID.toString() to mintAuthority.account.base58()) - ) company = network!!.createPartyNode(COMPANY.name) observer = network!!.createPartyNode(OBSERVER.name) shareholder = network!!.createPartyNode(SHAREHOLDER.name) bank = network!!.createPartyNode(BANK.name) notary = network!!.notaryNodes[0] notaryParty = notary!!.info.legalIdentities[0] - ba = network!!.createNode( + bridgingAuthority = network!!.createNode( MockNodeParameters( legalName = BA.name, - additionalCordapps = listOf(bridgingCordapp.withConfig(companyCfg)) + additionalCordapps = listOf(bridgingFlowsCordapp.withConfig(baConfig), bridgingContractsCordapp) ) ) @@ -274,7 +272,7 @@ class FlowTests { future.get() // Issue 2nd Stock on Bridge Authority node to verify it remains unaffected - future = ba!!.startFlow( + future = bridgingAuthority!!.startFlow( CreateAndIssueStock( STOCK_SYMBOL_2, STOCK_NAME_2, @@ -299,13 +297,19 @@ class FlowTests { Assert.assertEquals("0", startSolanaBalance!!.amount) // Second stock on Bridging Authority - to verify it remains unaffected - var stock2StatePointer = getTokensPointer(ba!!, STOCK_SYMBOL_2) - var (start2CordaQuantity) = ba!!.services.vaultService.tokenBalance(stock2StatePointer) + var stock2StatePointer = getTokensPointer(bridgingAuthority!!, STOCK_SYMBOL_2) + var (start2CordaQuantity) = bridgingAuthority!!.services.vaultService.tokenBalance(stock2StatePointer) Assert.assertEquals(ISSUING_STOCK_QUANTITY.toLong(), start2CordaQuantity) // Move Stock future = - company!!.startFlow(MoveStock(STOCK_SYMBOL, ISSUING_STOCK_QUANTITY.toLong(), ba!!.info.legalIdentities[0])) + company!!.startFlow( + MoveStock( + STOCK_SYMBOL, + ISSUING_STOCK_QUANTITY.toLong(), + bridgingAuthority!!.info.legalIdentities[0] + ) + ) network!!.runNetwork() future.get() @@ -314,35 +318,46 @@ class FlowTests { Assert.assertEquals(0L, endCordaQuantity) // Bridging Authority received the amount of stocks - stockStatePointer = getTokensPointer(ba!!, STOCK_SYMBOL) - val (startBridgingAuthorityCordaQuantity) = ba!!.services.vaultService.tokenBalance(stockStatePointer) + stockStatePointer = getTokensPointer(bridgingAuthority!!, STOCK_SYMBOL) + val (startBridgingAuthorityCordaQuantity) = bridgingAuthority!!.services.vaultService.tokenBalance( + stockStatePointer + ) Assert.assertEquals(ISSUING_STOCK_QUANTITY.toLong(), startBridgingAuthorityCordaQuantity) - future = ba!!.startFlow( + + val future2 = bridgingAuthority!!.startFlow( + GetTokenToBridge( + STOCK_SYMBOL + ) + ) + network!!.runNetwork() + val statesToBridge = future2.get() + Assert.assertEquals(1, statesToBridge.size) + + future = bridgingAuthority!!.startFlow( BridgeStock( - STOCK_SYMBOL, - startCordaQuantity , - ba!!.info.legalIdentities[0] //TODO remove this as will be internally moved to own CI + statesToBridge.first(), + bridgingAuthority!!.info.legalIdentities[0] //TODO remove this as will be internally moved to own CI ) ) network!!.runNetwork() future.get() - stockStatePointer = getTokensPointer(ba!!, STOCK_SYMBOL) - val (finalCordaQuantity) = ba!!.services.vaultService.tokenBalance(stockStatePointer) + stockStatePointer = getTokensPointer(bridgingAuthority!!, STOCK_SYMBOL) + val (finalCordaQuantity) = bridgingAuthority!!.services.vaultService.tokenBalance(stockStatePointer) Assert.assertEquals( ISSUING_STOCK_QUANTITY.toLong(), finalCordaQuantity ) // TODO this is Corda move token to self, so it still the same amount as at the beginning val token: StateAndRef? = - ba!!.services.vaultService.queryBy(FungibleToken::class.java).states.firstOrNull { + bridgingAuthority!!.services.vaultService.queryBy(FungibleToken::class.java).states.firstOrNull { it.state.data.amount.token.tokenType == stockStatePointer } Assert.assertNotNull(token) val bridgingState: StateAndRef? = - ba!!.services.vaultService.queryBy(BridgedAssetLockState::class.java).states.firstOrNull() + bridgingAuthority!!.services.vaultService.queryBy(BridgedAssetLockState::class.java).states.firstOrNull() Assert.assertNotNull(bridgingState) Assert.assertTrue( @@ -357,8 +372,8 @@ class FlowTests { Assert.assertEquals(ISSUING_STOCK_QUANTITY.toString(), finalSolanaBalance!!.amount) // Second stock balance remains unchanged /unaffected - stock2StatePointer = getTokensPointer(ba!!, STOCK_SYMBOL_2) - start2CordaQuantity = ba!!.services.vaultService.tokenBalance(stock2StatePointer).quantity + stock2StatePointer = getTokensPointer(bridgingAuthority!!, STOCK_SYMBOL_2) + start2CordaQuantity = bridgingAuthority!!.services.vaultService.tokenBalance(stock2StatePointer).quantity Assert.assertEquals(ISSUING_STOCK_QUANTITY.toLong(), start2CordaQuantity) } From c9f308983c8da65ffb2369594cb3ebe27fef6ec9 Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Tue, 30 Sep 2025 13:50:15 +0100 Subject: [PATCH 23/27] Refactor SDk to own module and cordapps --- .../bridging/flows/SolanaAccountsMappingService.kt | 3 ++- .../corda/samples/stockpaydividend/flows/QueryFlows.kt | 4 ++-- .../net/corda/samples/stockpaydividend/FlowTests.kt | 9 +++++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/SolanaAccountsMappingService.kt b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/SolanaAccountsMappingService.kt index 0bfaebd9..02fe9422 100644 --- a/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/SolanaAccountsMappingService.kt +++ b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/SolanaAccountsMappingService.kt @@ -7,6 +7,7 @@ import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.solana.sdk.instruction.Pubkey import java.util.UUID + @CordaService class SolanaAccountsMappingService(appServiceHub: AppServiceHub) : SingletonSerializeAsToken() { var participants: Map @@ -23,7 +24,7 @@ class SolanaAccountsMappingService(appServiceHub: AppServiceHub) : SingletonSeri }?.toMap() ?: emptyMap() // throw IllegalArgumentException("participants config missing for SolanaAccountsMappingService") } catch (_: Exception) { - emptyMap() + emptyMap() //TODO here and other occurrences, ignore misconfiguration as the service is used by Notary in the mock network } mints = try { (cfg.get("mints") as? Map)?.map { (k, v) -> UUID.fromString(k) to Pubkey.fromBase58(v) } diff --git a/Tokens/stockpaydividend/workflows/src/main/kotlin/net/corda/samples/stockpaydividend/flows/QueryFlows.kt b/Tokens/stockpaydividend/workflows/src/main/kotlin/net/corda/samples/stockpaydividend/flows/QueryFlows.kt index 9ad04536..7db816f9 100644 --- a/Tokens/stockpaydividend/workflows/src/main/kotlin/net/corda/samples/stockpaydividend/flows/QueryFlows.kt +++ b/Tokens/stockpaydividend/workflows/src/main/kotlin/net/corda/samples/stockpaydividend/flows/QueryFlows.kt @@ -47,7 +47,7 @@ class GetFiatBalance(private val currencyCode: String) : FlowLogic() { } -//TODO this is temporally +//TODO this is temporally, used by Bridging Authority @InitiatingFlow @StartableByRPC class GetTokenToBridge( @@ -59,7 +59,7 @@ class GetTokenToBridge( @Suspendable override fun call(): List> { val page = - serviceHub.vaultService.queryBy(StockState::class.java) //TODO + UNCONSUMED query and belonging to our identity + serviceHub.vaultService.queryBy(StockState::class.java) //TODO + UNCONSUMED query and belonging to our identity val states = page.states.filter { it.state.data.symbol == symbol } val pointer: TokenPointer = states.map { it.state.data.toPointer(StockState::class.java) }.first() val tokens: List> = serviceHub.vaultService.tokenAmountsByToken(pointer).states diff --git a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt index 46c6ee3e..55fd529c 100644 --- a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt +++ b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt @@ -135,7 +135,13 @@ class FlowTests { TestCordapp.findCordapp("net.corda.samples.stockpaydividend.flows"), TestCordapp.findCordapp("com.r3.corda.lib.tokens.contracts"), TestCordapp.findCordapp("com.r3.corda.lib.tokens.workflows") - ), notarySpecs = listOf(MockNetworkNotarySpec(notaryName, notaryConfig = createNotaryConfig())), + ), + notarySpecs = listOf( + MockNetworkNotarySpec( + notaryName, + notaryConfig = createNotaryConfig() + ) + ), //TODO start separately notary to provide specific set of cordapps without bridging ones networkParameters = testNetworkParameters(minimumPlatformVersion = 4) ) ) @@ -233,7 +239,6 @@ class FlowTests { //Retrieve states from receiver val receivedStockStatesPages = shareholder!!.services.vaultService.queryBy(StockState::class.java).states val receivedStockState = receivedStockStatesPages[0].state.data - val cordaTokenId = receivedStockState.toPointer().pointer.pointer.id val (quantity) = shareholder!!.services.vaultService.tokenBalance( receivedStockState.toPointer( receivedStockState.javaClass From 7b48a183987a0a424b257b3bd18a046e56167534 Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Tue, 30 Sep 2025 15:58:38 +0100 Subject: [PATCH 24/27] Refactoring and mapping participant --- Tokens/constants.properties | 2 +- .../bridging/flows/BridgeFungibleTokenFlow.kt | 134 ++++++++++++++++++ .../flows/BridgeFungibleTokensFlow.kt | 48 ------- .../bridging/flows/BridgeTokensUtilities.kt | 9 +- .../flows/SolanaAccountsMappingService.kt | 8 +- .../tokens/bridging/flows/rpc/BridgeTokens.kt | 78 +--------- .../samples/stockpaydividend/FlowTests.kt | 7 +- 7 files changed, 150 insertions(+), 136 deletions(-) create mode 100644 Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeFungibleTokenFlow.kt delete mode 100644 Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeFungibleTokensFlow.kt diff --git a/Tokens/constants.properties b/Tokens/constants.properties index 8b66881f..af643437 100644 --- a/Tokens/constants.properties +++ b/Tokens/constants.properties @@ -10,4 +10,4 @@ log4jVersion =2.23.1 platformVersion=140 slf4jVersion=2.0.12 nettyVersion=4.1.77.Final -solana4jVersion=1.2.0 \ No newline at end of file +solana4jVersion=1.2.0 diff --git a/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeFungibleTokenFlow.kt b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeFungibleTokenFlow.kt new file mode 100644 index 00000000..882f4ece --- /dev/null +++ b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeFungibleTokenFlow.kt @@ -0,0 +1,134 @@ +package com.r3.corda.lib.tokens.bridging.flows + +import co.paralleluniverse.fibers.Suspendable +import com.r3.corda.lib.tokens.bridging.contracts.BridgingContract +import com.r3.corda.lib.tokens.bridging.states.BridgedAssetLockState +import com.r3.corda.lib.tokens.contracts.states.FungibleToken +import com.r3.corda.lib.tokens.contracts.types.TokenPointer +import com.r3.corda.lib.tokens.workflows.flows.move.AbstractMoveTokensFlow +import com.r3.corda.lib.tokens.workflows.flows.move.MoveTokensFlowHandler +import com.r3.corda.lib.tokens.workflows.flows.move.addMoveTokens +import com.r3.corda.lib.tokens.workflows.utilities.sessionsForParties +import net.corda.core.contracts.ContractState +import net.corda.core.contracts.StateAndRef +import net.corda.core.contracts.StateRef +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession +import net.corda.core.flows.InitiatedBy +import net.corda.core.flows.InitiatingFlow +import net.corda.core.flows.StartableByService +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.Party +import net.corda.core.transactions.SignedTransaction +import net.corda.core.transactions.TransactionBuilder +import net.corda.solana.sdk.instruction.Pubkey + +/** + * Initiating flow used to bridge token of the same party. + * + * @param observers optional observing parties to which the transaction will be broadcast + */ +@StartableByService +@InitiatingFlow +class BridgeFungibleTokenFlow( + val holder: AbstractParty, + val observers: List = emptyList(), + val token: StateAndRef, //TODO should be FungibleToken, TODO change to any TokenType would need amendments to UUID retrieval below + val bridgeAuthority: Party +) : FlowLogic() { + + @Suspendable + override fun call(): SignedTransaction { + val participants = listOf(holder) //TODO add confidentialIdentity + val observerSessions = sessionsForParties(observers) + val participantSessions = sessionsForParties(participants) + + val additionalOutput: ContractState = BridgedAssetLockState(listOf(ourIdentity)) + + val cordaTokenId = (token.state.data.amount.token.tokenType as TokenPointer<*>).pointer.pointer.id + + val owners = previousOwnersOf(token).map { serviceHub.identityService.wellKnownPartyFromAnonymous(it) ?: it } + + val solanaAccountMapping = serviceHub.cordaService(SolanaAccountsMappingService::class.java) + val destination = + solanaAccountMapping.participants[(owners.first() as Party).name]!! //TODO handle null and ugly code + val mint = solanaAccountMapping.mints[cordaTokenId]!! //TODO handle null + val mintAuthority = solanaAccountMapping.mintAuthorities[cordaTokenId]!! //TODO handle null + val additionalCommand = BridgingContract.BridgingCommand.BridgeToSolana( + destination, + bridgeAuthority + ) + + return subFlow( + InternalBridgeFungibleTokenFlow( + participantSessions = participantSessions, + observerSessions = observerSessions, + token = token, + additionalOutput = additionalOutput, + additionalCommand = additionalCommand, + destination = destination, + mint = mint, + mintAuthority = mintAuthority + ) + ) + } + + fun previousOwnersOf(output: StateAndRef): Set { + val txHash = output.ref.txhash + val stx = serviceHub.validatedTransactions.getTransaction(txHash) + ?: error("Producing transaction $txHash not found") + + val inputTokens: List = + stx.toLedgerTransaction(serviceHub).inputsOfType() + + // Usually one holder; can be many if the tx merged inputs + return inputTokens.map { it.holder }.toSet() + } +} + + +/** + * Responder flow for [BridgeFungibleTokenFlow]. + */ +@InitiatedBy(BridgeFungibleTokenFlow::class) +class BridgeFungibleTokensHandler(val otherSession: FlowSession) : FlowLogic() { + @Suspendable + override fun call() = subFlow(MoveTokensFlowHandler(otherSession)) +} + +class InternalBridgeFungibleTokenFlow +@JvmOverloads +constructor( + override val participantSessions: List, + override val observerSessions: List = emptyList(), + val token: StateAndRef, + val additionalOutput: ContractState, + val additionalCommand: BridgingContract.BridgingCommand, + val destination: Pubkey, + val mint: Pubkey, + val mintAuthority: Pubkey +) : AbstractMoveTokensFlow() { //TODO move away from this abstract class, it's progress tracker mention only token move + + @Suspendable + override fun addMove(transactionBuilder: TransactionBuilder) { + + val amount = token.state.data.amount + val holder = ourIdentity //TODO confidential identity + val output = FungibleToken(amount, holder) + addMoveTokens(transactionBuilder = transactionBuilder, inputs = listOf(token), outputs = listOf(output)) + + val quantity = amount.toDecimal() + .toLong() //TODO this is quantity for Solana, should it be 1 to 1 what is bridged on Corda? + bridgeToken( + serviceHub, + transactionBuilder, + additionalOutput, + additionalCommand, + destination, + mint, + mintAuthority, + quantity + ) + } +} + diff --git a/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeFungibleTokensFlow.kt b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeFungibleTokensFlow.kt deleted file mode 100644 index 6717f1cb..00000000 --- a/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeFungibleTokensFlow.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.r3.corda.lib.tokens.bridging.flows - -import co.paralleluniverse.fibers.Suspendable -import com.r3.corda.lib.tokens.bridging.contracts.BridgingContract -import com.r3.corda.lib.tokens.contracts.states.FungibleToken -import com.r3.corda.lib.tokens.workflows.flows.move.AbstractMoveTokensFlow -import com.r3.corda.lib.tokens.workflows.flows.move.addMoveTokens -import net.corda.core.contracts.ContractState -import net.corda.core.contracts.StateAndRef -import net.corda.core.flows.FlowSession -import net.corda.core.transactions.TransactionBuilder -import net.corda.solana.sdk.instruction.Pubkey - -class BridgeFungibleTokensFlow -@JvmOverloads -constructor( - override val participantSessions: List, - override val observerSessions: List = emptyList(), - val token: StateAndRef, - val additionalOutput: ContractState, - val additionalCommand: BridgingContract.BridgingCommand, - val destination: Pubkey, - val mint: Pubkey, - val mintAuthority: Pubkey -) : AbstractMoveTokensFlow() { - - @Suspendable - override fun addMove(transactionBuilder: TransactionBuilder) { - - val amount = token.state.data.amount - val holder = ourIdentity //TODO confidential identity - val output = FungibleToken(amount, holder) - addMoveTokens(transactionBuilder = transactionBuilder, inputs = listOf(token), outputs = listOf(output)) - - val quantity = amount.toDecimal() - .toLong() //TODO this is quantity for Solana, should it be 1 to 1 what is bridged on Corda? - bridgeToken( - serviceHub, - transactionBuilder, - additionalOutput, - additionalCommand, - destination, - mint, - mintAuthority, - quantity - ) - } -} \ No newline at end of file diff --git a/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeTokensUtilities.kt b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeTokensUtilities.kt index c3ae2674..ecb5f602 100644 --- a/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeTokensUtilities.kt +++ b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeTokensUtilities.kt @@ -34,24 +34,23 @@ fun bridgeTokens( .map { it.data } .filterIsInstance() .groupBy { it.issuedTokenType } - val inputGroups: Map>> = transactionBuilder.inputStates() - .map { serviceHub.toStateAndRef(it) }.groupBy { it.state.data.issuedTokenType } + val inputGroups: Map>> = + transactionBuilder.inputStates() + .map { serviceHub.toStateAndRef(it) } + .groupBy { it.state.data.issuedTokenType } check(outputGroups.keys == inputGroups.keys) { "Input and output token types must correspond to each other when moving tokensToIssue" } transactionBuilder.apply { - outputGroups.forEach { (issuedTokenType: IssuedTokenType, _: List) -> val inputGroup = inputGroups[issuedTokenType] ?: throw IllegalArgumentException("No corresponding inputs for the outputs issued token type: $issuedTokenType") val keys = inputGroup.map { it.state.data.holder.owningKey } - additionalOutput.map { addOutputState(it) } - addCommand(additionalCommand, keys) } } diff --git a/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/SolanaAccountsMappingService.kt b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/SolanaAccountsMappingService.kt index 02fe9422..a163371f 100644 --- a/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/SolanaAccountsMappingService.kt +++ b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/SolanaAccountsMappingService.kt @@ -22,14 +22,14 @@ class SolanaAccountsMappingService(appServiceHub: AppServiceHub) : SingletonSeri v ) }?.toMap() - ?: emptyMap() // throw IllegalArgumentException("participants config missing for SolanaAccountsMappingService") + ?: emptyMap() } catch (_: Exception) { - emptyMap() //TODO here and other occurrences, ignore misconfiguration as the service is used by Notary in the mock network + emptyMap() //TODO here and other occurrences, for now ignore misconfiguration as the service is used by Notary in the mock network } mints = try { (cfg.get("mints") as? Map)?.map { (k, v) -> UUID.fromString(k) to Pubkey.fromBase58(v) } ?.toMap() - ?: emptyMap() //throw IllegalArgumentException("mints config missing for SolanaAccountsMappingService") + ?: emptyMap() } catch (_: Exception) { emptyMap() } @@ -39,7 +39,7 @@ class SolanaAccountsMappingService(appServiceHub: AppServiceHub) : SingletonSeri v ) }?.toMap() - ?: emptyMap() //throw IllegalArgumentException("mintAuthorities config missing for SolanaAccountsMappingService") + ?: emptyMap() } catch (_: Exception) { emptyMap() } diff --git a/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/rpc/BridgeTokens.kt b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/rpc/BridgeTokens.kt index 04cfec9a..b956034c 100644 --- a/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/rpc/BridgeTokens.kt +++ b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/rpc/BridgeTokens.kt @@ -1,31 +1,19 @@ package com.r3.corda.lib.tokens.bridging.flows.rpc import co.paralleluniverse.fibers.Suspendable -import com.r3.corda.lib.tokens.workflows.flows.move.MoveTokensFlowHandler -import com.r3.corda.lib.tokens.workflows.utilities.sessionsForParties -import net.corda.core.contracts.ContractState +import com.r3.corda.lib.tokens.bridging.flows.BridgeFungibleTokenFlow import net.corda.core.flows.FlowLogic -import net.corda.core.flows.FlowSession -import net.corda.core.flows.InitiatedBy import net.corda.core.flows.InitiatingFlow import net.corda.core.flows.StartableByRPC -import net.corda.core.flows.StartableByService -import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party -import net.corda.core.transactions.SignedTransaction -import com.r3.corda.lib.tokens.bridging.flows.BridgeFungibleTokensFlow -import com.r3.corda.lib.tokens.bridging.flows.SolanaAccountsMappingService -import com.r3.corda.lib.tokens.bridging.contracts.BridgingContract -import com.r3.corda.lib.tokens.bridging.states.BridgedAssetLockState import com.r3.corda.lib.tokens.contracts.states.FungibleToken -import com.r3.corda.lib.tokens.contracts.types.TokenPointer import net.corda.core.contracts.StateAndRef import net.corda.core.utilities.ProgressTracker @InitiatingFlow @StartableByRPC -class BridgeStock( - val token: StateAndRef, //TODO change to a list +class BridgeToken( + val token: StateAndRef, //TODO change to a list? val bridgeAuthority: Party ) : FlowLogic() { @@ -36,7 +24,7 @@ class BridgeStock( //Use built-in flow for move tokens to the recipient val stx = subFlow( - BridgeFungibleTokens( + BridgeFungibleTokenFlow( ourIdentity, //TODO confidentialIdentity emptyList(), token, @@ -49,61 +37,3 @@ class BridgeStock( } } -/** - * Initiating flow used to bridge token of the same party. - * - * @param observers optional observing parties to which the transaction will be broadcast - */ -@StartableByService -@InitiatingFlow -class BridgeFungibleTokens //TODO move away from RPC package -@JvmOverloads -constructor( - val holder: AbstractParty, - val observers: List = emptyList(), - val token: StateAndRef, //TODO should be FungibleToken, TODO change to any TokenType would need amendments to UUID retrieval below - val bridgeAuthority: Party -) : FlowLogic() { - - @Suspendable - override fun call(): SignedTransaction { - val participants = listOf(holder) //TODO add confidentialIdentity - val observerSessions = sessionsForParties(observers) - val participantSessions = sessionsForParties(participants) - - val additionalOutput: ContractState = BridgedAssetLockState(listOf(ourIdentity)) - - val cordaTokenId = (token.state.data.amount.token.tokenType as TokenPointer<*>).pointer.pointer.id - - val solanaAccountMapping = serviceHub.cordaService(SolanaAccountsMappingService::class.java) - val destination = solanaAccountMapping.participants[ourIdentity.name]!! //TODO handle null, TODo eliminate this - val mint = solanaAccountMapping.mints[cordaTokenId]!! //TODO handle null - val mintAuthority = solanaAccountMapping.mintAuthorities[cordaTokenId]!! //TODO handle null - val additionalCommand = BridgingContract.BridgingCommand.BridgeToSolana( - destination, - bridgeAuthority - ) - - return subFlow( - BridgeFungibleTokensFlow( - participantSessions = participantSessions, - observerSessions = observerSessions, - token = token, - additionalOutput = additionalOutput, - additionalCommand = additionalCommand, - destination = destination, - mint = mint, - mintAuthority = mintAuthority - ) - ) - } -} - -/** - * Responder flow for [BridgeFungibleTokens]. - */ -@InitiatedBy(BridgeFungibleTokens::class) -class BridgeFungibleTokensHandler(val otherSession: FlowSession) : FlowLogic() { - @Suspendable - override fun call() = subFlow(MoveTokensFlowHandler(otherSession)) -} diff --git a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt index 55fd529c..ecb9c111 100644 --- a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt +++ b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt @@ -2,7 +2,7 @@ package net.corda.samples.stockpaydividend import com.lmax.solana4j.api.PublicKey import com.r3.corda.lib.tokens.bridging.states.BridgedAssetLockState -import com.r3.corda.lib.tokens.bridging.flows.rpc.BridgeStock +import com.r3.corda.lib.tokens.bridging.flows.rpc.BridgeToken import com.r3.corda.lib.tokens.contracts.states.FungibleToken import com.r3.corda.lib.tokens.contracts.types.TokenPointer import com.r3.corda.lib.tokens.workflows.utilities.tokenBalance @@ -124,7 +124,7 @@ class FlowTests { val bridgingContractsCordapp = TestCordapp.findCordapp("com.r3.corda.lib.tokens.bridging.contracts") val bridgingFlowsCordapp = TestCordapp.findCordapp("com.r3.corda.lib.tokens.bridging.flows") val baConfig = mapOf( - "participants" to mapOf(BA.name.toString() to tokenAccount.base58()), //TODO introduction of BA shows that participants are not needed - it's rather a single BA account + "participants" to mapOf(COMPANY.name.toString() to tokenAccount.base58()), "mints" to mapOf(LINEAR_ID.toString() to tokenMint.base58()), "mintAuthorities" to mapOf(LINEAR_ID.toString() to mintAuthority.account.base58()) ) @@ -340,7 +340,7 @@ class FlowTests { Assert.assertEquals(1, statesToBridge.size) future = bridgingAuthority!!.startFlow( - BridgeStock( + BridgeToken( statesToBridge.first(), bridgingAuthority!!.info.legalIdentities[0] //TODO remove this as will be internally moved to own CI ) @@ -380,7 +380,6 @@ class FlowTests { stock2StatePointer = getTokensPointer(bridgingAuthority!!, STOCK_SYMBOL_2) start2CordaQuantity = bridgingAuthority!!.services.vaultService.tokenBalance(stock2StatePointer).quantity Assert.assertEquals(ISSUING_STOCK_QUANTITY.toLong(), start2CordaQuantity) - } private fun getTokensPointer(node: StartedMockNode, symbol: String): TokenPointer { From 6d67922cc13d8f1ddd01bdb5f81a422e7eeb9b36 Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Tue, 30 Sep 2025 16:17:55 +0100 Subject: [PATCH 25/27] Remove import --- .../corda/lib/tokens/bridging/flows/BridgeFungibleTokenFlow.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeFungibleTokenFlow.kt b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeFungibleTokenFlow.kt index 882f4ece..b0571429 100644 --- a/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeFungibleTokenFlow.kt +++ b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeFungibleTokenFlow.kt @@ -11,7 +11,6 @@ import com.r3.corda.lib.tokens.workflows.flows.move.addMoveTokens import com.r3.corda.lib.tokens.workflows.utilities.sessionsForParties import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateAndRef -import net.corda.core.contracts.StateRef import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowSession import net.corda.core.flows.InitiatedBy From 2e4f61ca6c855f4dcc54fd559edbfd9080bd5c77 Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Tue, 30 Sep 2025 16:25:40 +0100 Subject: [PATCH 26/27] Filtering a single previous owner of a token in nicer way --- .../tokens/bridging/flows/BridgeFungibleTokenFlow.kt | 12 ++++++------ .../net/corda/samples/stockpaydividend/FlowTests.kt | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeFungibleTokenFlow.kt b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeFungibleTokenFlow.kt index b0571429..9145456c 100644 --- a/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeFungibleTokenFlow.kt +++ b/Tokens/stockpaydividend/bridging-flows/src/main/kotlin/com/r3/corda/lib/tokens/bridging/flows/BridgeFungibleTokenFlow.kt @@ -38,7 +38,7 @@ class BridgeFungibleTokenFlow( @Suspendable override fun call(): SignedTransaction { - val participants = listOf(holder) //TODO add confidentialIdentity + val participants = listOf(holder) val observerSessions = sessionsForParties(observers) val participantSessions = sessionsForParties(participants) @@ -47,10 +47,13 @@ class BridgeFungibleTokenFlow( val cordaTokenId = (token.state.data.amount.token.tokenType as TokenPointer<*>).pointer.pointer.id val owners = previousOwnersOf(token).map { serviceHub.identityService.wellKnownPartyFromAnonymous(it) ?: it } - + val singlePreviousOwner = owners.singleOrNull { it is Party } as Party? + require(singlePreviousOwner != null) { + "Cannot find previous owner of the token to bridge, or multiple found: $owners" + } val solanaAccountMapping = serviceHub.cordaService(SolanaAccountsMappingService::class.java) val destination = - solanaAccountMapping.participants[(owners.first() as Party).name]!! //TODO handle null and ugly code + solanaAccountMapping.participants[singlePreviousOwner.name]!! //TODO handle null val mint = solanaAccountMapping.mints[cordaTokenId]!! //TODO handle null val mintAuthority = solanaAccountMapping.mintAuthorities[cordaTokenId]!! //TODO handle null val additionalCommand = BridgingContract.BridgingCommand.BridgeToSolana( @@ -80,12 +83,10 @@ class BridgeFungibleTokenFlow( val inputTokens: List = stx.toLedgerTransaction(serviceHub).inputsOfType() - // Usually one holder; can be many if the tx merged inputs return inputTokens.map { it.holder }.toSet() } } - /** * Responder flow for [BridgeFungibleTokenFlow]. */ @@ -130,4 +131,3 @@ constructor( ) } } - diff --git a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt index ecb9c111..3a883d2a 100644 --- a/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt +++ b/Tokens/stockpaydividend/workflows/src/test/kotlin/net/corda/samples/stockpaydividend/FlowTests.kt @@ -390,4 +390,4 @@ class FlowTests { Assert.assertEquals(1, pointers.size) return pointers.first() } -} \ No newline at end of file +} From a15e29333c89f314435e05597f2a7a64c2ea9ceb Mon Sep 17 00:00:00 2001 From: Szymon Sztuka Date: Tue, 30 Sep 2025 17:33:25 +0100 Subject: [PATCH 27/27] Remove Solana SDK/Common from Sample Cordapp dependencies - not used --- Tokens/stockpaydividend/workflows/build.gradle | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Tokens/stockpaydividend/workflows/build.gradle b/Tokens/stockpaydividend/workflows/build.gradle index 18aeb362..924fb41a 100644 --- a/Tokens/stockpaydividend/workflows/build.gradle +++ b/Tokens/stockpaydividend/workflows/build.gradle @@ -53,15 +53,12 @@ dependencies { // CorDapp dependencies. cordapp project(":contracts") - cordapp project(":bridging-contracts") - cordapp project(":bridging-flows") + cordapp project(":bridging-contracts") // Used in the test + cordapp project(":bridging-flows") // Used in the test // Token SDK dependencies. cordaProvided "$tokens_release_group:tokens-contracts:$tokens_release_version" cordaProvided "$tokens_release_group:tokens-workflows:$tokens_release_version" - - cordaProvided "$corda_release_group:corda-solana-sdk:$corda_release_version" - cordaProvided "$corda_release_group:corda-solana-common:$corda_release_version" } task integrationTest(type: Test, dependsOn: []) {