Skip to content

Conversation

szymonsztuka
Copy link

@szymonsztuka szymonsztuka commented Sep 30, 2025

The simplest bridge flow and sample to bridge a given token with Bridging Authority containing mappings to Solana accounts.
Because Confidential Identity is not used, the bridge is a move to self (owner is still Bridging Authority).
No notary change -single notary for move and brige is used.

The test uses Mock Network and issues 2 tokens and bridge one, first an participant moves token to Bridging Authority, then test calls Bridging Authority to bridge (no vault listener implemented yet), and the test asserts that token balance appears on Solana.

Temporally Bridging SDK code is here as separate modules, will be moved to platform.

szymonsztuka and others added 24 commits September 3, 2025 09:52
…fidential identities. Bridge* flows follows the structure of similar Move flows from tokens SDK.
@szymonsztuka szymonsztuka changed the title Bridging stock sample first cut Minimal Bridging Stock Cordapp sample Sep 30, 2025
@szymonsztuka szymonsztuka marked this pull request as ready for review September 30, 2025 15:00
@szymonsztuka szymonsztuka requested a review from blsemo September 30, 2025 15:03
@mkitR3 mkitR3 force-pushed the bridging-stock-sample-first-cut branch from 0a08b83 to a15e293 Compare October 2, 2025 14:56
Comment on lines +50 to +64
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." }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is brittle and hard to read. SolanaInstruction implements equals/hashCode and so you can instead compare the instruction against the expected Token2022.mintTo instruction object.


@InitiatingFlow
@StartableByRPC
class BridgeToken(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This flow seems to be just for testing? Better suited to be just in the test class, rather than part of the CorDapp/library.

}
val solanaAccountMapping = serviceHub.cordaService(SolanaAccountsMappingService::class.java)
val destination =
solanaAccountMapping.participants[singlePreviousOwner.name]!! //TODO handle null

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throw an exception if previous owner is not configured.

val solanaAccountMapping = serviceHub.cordaService(SolanaAccountsMappingService::class.java)
val destination =
solanaAccountMapping.participants[singlePreviousOwner.name]!! //TODO handle null
val mint = solanaAccountMapping.mints[cordaTokenId]!! //TODO handle null

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

class BridgeFungibleTokenFlow(
val holder: AbstractParty,
val observers: List<Party> = emptyList(),
val token: StateAndRef<FungibleToken>, //TODO should be FungibleToken, TODO change to any TokenType would need amendments to UUID retrieval below

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these TODOs are no longer needed?

fun bridgeTokens(
serviceHub: ServiceHub,
transactionBuilder: TransactionBuilder,
additionalOutput: List<ContractState>,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to add additional outputs for the user. they can do it themselves after or before calling this method.

/**
* Adds a set of bridging commands to a transaction using specific outputs.
*/
@Suspendable

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this really need to be suspendable?

val outputGroups: Map<IssuedTokenType, List<AbstractToken>> =
transactionBuilder.outputStates()
.map { it.data }
.filterIsInstance<AbstractToken>()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this not be FungibleToken?

"Input and output token types must correspond to each other when moving tokensToIssue"
}

transactionBuilder.apply {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove this apply. it adds an extra indention for just the addCommand call.


val quantity = amount.toDecimal()
.toLong() //TODO this is quantity for Solana, should it be 1 to 1 what is bridged on Corda?
bridgeToken(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this utility function itself call addMoveTokens rather than assume it has been called? It makes a better utility function that correctly sets up the tx builder. Otherwise, bridgeToken is assuming the output and input states are already correctly present.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants