@@ -72,6 +72,30 @@ internal class GiveBillTransactor(
7272 data = payloadInfo.codeData.toList()
7373 }
7474
75+ /* *
76+ * Presents a cash bill and waits for a recipient to claim it via peer-to-peer
77+ * messaging, then executes the on-chain transfer.
78+ *
79+ * Flow:
80+ * 1. Resolve a [VerifiedState] for the currency/token pair using a fallback
81+ * chain: provided state -> local proto store -> live mint data fetch.
82+ * 2. Compute exchange data from the verified state (fails if the rate has
83+ * expired past [exchangeDataTimeout]).
84+ * 3. Publish a "give bill" request on the rendezvous messaging stream,
85+ * advertising the token mint and exchange data to potential recipients.
86+ * 4. Block until a "grab bill" response arrives on the same rendezvous stream.
87+ * 5. Verify the grab request's destination signature against the rendezvous
88+ * key to ensure the destination hasn't been tampered with.
89+ * 6. Guard against duplicate transfers (same receiving account seen twice).
90+ * 7. Transfer funds from the sender's token vault to the recipient's
91+ * destination account.
92+ * 8. Poll for the intent metadata confirmation from the server.
93+ *
94+ * Preconditions: [with] must be called first to set the token, amount, owner,
95+ * and (optionally) a pre-resolved [VerifiedState].
96+ *
97+ * @return the confirmed [TransactionMetadata.SendPublicPayment] on success.
98+ */
7599 suspend fun start (): Result <TransactionMetadata .SendPublicPayment > {
76100 val ownerKey = owner
77101 ? : return logAndFail(GiveTransactorError .Other (message = " No owner key. Did you call with() first?" ))
@@ -99,8 +123,10 @@ internal class GiveBillTransactor(
99123 billExchangeDataTimeout = exchangeDataTimeout
100124 ) ? : return logAndFail(GiveTransactorError .ExchangeRateExpiredException ())
101125
102- // 1. Send request to "give" the bill to the recipient
103- // This provides the recipient with the desired token mint of the cash
126+ // 1. Send request to "give" the bill to the recipient.
127+ // This provides the recipient with the desired token mint of the cash.
128+ // If this fails, bail out immediately — the receiver never got the
129+ // advertisement so the stream will never deliver a grab request.
104130 messagingController.sendRequestToGiveBill(desiredToken.address, rendezvous, exchangeData)
105131 .onSuccess {
106132 trace(
@@ -109,12 +135,9 @@ internal class GiveBillTransactor(
109135 type = TraceType .Log
110136 )
111137 }.onFailure { cause ->
112- trace(
113- tag = " Messaging" ,
114- message = " Failed to send request to give bill for ${desiredToken.symbol} " ,
115- type = TraceType .Error ,
116- error = cause
117- )
138+ return logAndFail(cause) {
139+ " token" to desiredToken.symbol
140+ }
118141 }
119142
120143 trace(
@@ -150,6 +173,23 @@ internal class GiveBillTransactor(
150173
151174 receivingAccount = transferRequest.account
152175
176+ /* *
177+ * At transfer time:
178+ * 1. If providedVerifiedState exists → use it (caller knows best)
179+ * 2. Otherwise → try fresh from proto store
180+ * 3. Otherwise → fall back to the upfront-resolved state
181+ */
182+ val transferVerifiedState = providedVerifiedState
183+ ? : verifiedProtoManager.getVerifiedStateFor(sendingAmount.rate.currency, desiredToken.address)
184+ ? : verifiedState
185+
186+ val transferExchangeData = transferVerifiedState.exchangeDataFor(
187+ amount = sendingAmount,
188+ mint = desiredToken.address,
189+ billExchangeDataTimeout = exchangeDataTimeout
190+ ) ? : exchangeData
191+
192+
153193 // 4. Send the funds to destination
154194 return transactionController.transfer(
155195 scope = scope,
@@ -158,7 +198,7 @@ internal class GiveBillTransactor(
158198 source = sendingVault,
159199 destination = transferRequest.account,
160200 rendezvous = rendezvous.toPublicKey(),
161- exchangeData = exchangeData ,
201+ exchangeData = transferExchangeData ,
162202 ).fold(
163203 onSuccess = {
164204 transactionController.pollIntentMetadata(
0 commit comments