77 "github.com/mr-tron/base58"
88
99 commonpb "github.com/code-payments/code-protobuf-api/generated/go/common/v1"
10+ transactionpb "github.com/code-payments/code-protobuf-api/generated/go/transaction/v2"
1011 indexerpb "github.com/code-payments/code-vm-indexer/generated/indexer/v1"
1112
1213 "github.com/code-payments/code-server/pkg/code/common"
@@ -209,20 +210,35 @@ func (h *NoPrivacyTransferWithAuthorityFulfillmentHandler) CanSubmitToBlockchain
209210
210211 // The source user account is a Code account, so we must validate it exists on
211212 // the blockchain prior to sending funds from it.
212- isSourceAccountCreated , err := isTokenAccountOnBlockchain (ctx , h .data , fulfillmentRecord .Source )
213+ isSourceAccountCreated , err := isAccountInitialized (ctx , h .data , fulfillmentRecord .Source )
213214 if err != nil {
214215 return false , err
215216 } else if ! isSourceAccountCreated {
216217 return false , nil
217218 }
218219
219220 // The destination user account might be a Code account or external wallet, so we
220- // must validate it exists on the blockchain prior to send funds to it.
221- isDestinationAccountCreated , err := isTokenAccountOnBlockchain (ctx , h .data , * fulfillmentRecord .Destination )
221+ // must validate it exists on the blockchain prior to sending funds to it, or if we'll
222+ // be creating it at time of send.
223+ destinationAccount , err := common .NewAccountFromPublicKeyString (* fulfillmentRecord .Destination )
222224 if err != nil {
223225 return false , err
224- } else if ! isDestinationAccountCreated {
225- return false , nil
226+ }
227+ isInternalTransfer , err := isInternalVmTransfer (ctx , h .data , destinationAccount )
228+ if err != nil {
229+ return false , err
230+ }
231+ hasCreateOnSendFee , err := h .data .HasFeeAction (ctx , fulfillmentRecord .Intent , transactionpb .FeePaymentAction_CREATE_ON_SEND_WITHDRAWAL )
232+ if err != nil {
233+ return false , err
234+ }
235+ if isInternalTransfer || ! hasCreateOnSendFee {
236+ isDestinationAccountCreated , err := isAccountInitialized (ctx , h .data , * fulfillmentRecord .Destination )
237+ if err != nil {
238+ return false , err
239+ } else if ! isDestinationAccountCreated {
240+ return false , nil
241+ }
226242 }
227243
228244 // Check whether there's an earlier fulfillment that should be scheduled first
@@ -269,17 +285,17 @@ func (h *NoPrivacyTransferWithAuthorityFulfillmentHandler) SupportsOnDemandTrans
269285}
270286
271287func (h * NoPrivacyTransferWithAuthorityFulfillmentHandler ) MakeOnDemandTransaction (ctx context.Context , fulfillmentRecord * fulfillment.Record , selectedNonce * transaction_util.Nonce ) (* solana.Transaction , error ) {
272- virtualSignatureBytes , err := base58 . Decode ( * fulfillmentRecord .VirtualSignature )
288+ actionRecord , err := h . data . GetActionById ( ctx , fulfillmentRecord .Intent , fulfillmentRecord . ActionId )
273289 if err != nil {
274290 return nil , err
275291 }
276292
277- virtualNonce , err := common . NewAccountFromPublicKeyString (* fulfillmentRecord .VirtualNonce )
293+ virtualSignatureBytes , err := base58 . Decode (* fulfillmentRecord .VirtualSignature )
278294 if err != nil {
279295 return nil , err
280296 }
281297
282- actionRecord , err := h . data . GetActionById ( ctx , fulfillmentRecord .Intent , fulfillmentRecord . ActionId )
298+ virtualNonce , err := common . NewAccountFromPublicKeyString ( * fulfillmentRecord .VirtualNonce )
283299 if err != nil {
284300 return nil , err
285301 }
@@ -354,6 +370,24 @@ func (h *NoPrivacyTransferWithAuthorityFulfillmentHandler) MakeOnDemandTransacti
354370 * actionRecord .Quantity ,
355371 )
356372 } else {
373+ isCreateOnSend , err := h .data .HasFeeAction (ctx , fulfillmentRecord .Intent , transactionpb .FeePaymentAction_CREATE_ON_SEND_WITHDRAWAL )
374+ if err != nil {
375+ return & solana.Transaction {}, err
376+ }
377+
378+ var destinationOwnerAccount * common.Account
379+ if isCreateOnSend {
380+ intentRecord , err := h .data .GetIntent (ctx , fulfillmentRecord .Intent )
381+ if err != nil {
382+ return nil , err
383+ }
384+
385+ destinationOwnerAccount , err = common .NewAccountFromPublicKeyString (intentRecord .SendPublicPaymentMetadata .DestinationOwnerAccount )
386+ if err != nil {
387+ return nil , err
388+ }
389+ }
390+
357391 txn , makeTxnErr = transaction_util .MakeExternalTransferWithAuthorityTransaction (
358392 selectedNonce .Account ,
359393 selectedNonce .Blockhash ,
@@ -368,7 +402,10 @@ func (h *NoPrivacyTransferWithAuthorityFulfillmentHandler) MakeOnDemandTransacti
368402 sourceMemory ,
369403 sourceIndex ,
370404
405+ isCreateOnSend ,
406+ destinationOwnerAccount ,
371407 destinationTokenAccount ,
408+
372409 * actionRecord .Quantity ,
373410 )
374411 }
@@ -397,7 +434,7 @@ func (h *NoPrivacyWithdrawFulfillmentHandler) CanSubmitToBlockchain(ctx context.
397434
398435 // The source user account is a Code account, so we must validate it exists on
399436 // the blockchain prior to sending funds from it.
400- isSourceAccountCreated , err := isTokenAccountOnBlockchain (ctx , h .data , fulfillmentRecord .Source )
437+ isSourceAccountCreated , err := isAccountInitialized (ctx , h .data , fulfillmentRecord .Source )
401438 if err != nil {
402439 return false , err
403440 } else if ! isSourceAccountCreated {
@@ -406,7 +443,7 @@ func (h *NoPrivacyWithdrawFulfillmentHandler) CanSubmitToBlockchain(ctx context.
406443
407444 // The destination user account might be a Code account or external wallet, so we
408445 // must validate it exists on the blockchain prior to send funds to it.
409- isDestinationAccountCreated , err := isTokenAccountOnBlockchain (ctx , h .data , * fulfillmentRecord .Destination )
446+ isDestinationAccountCreated , err := isAccountInitialized (ctx , h .data , * fulfillmentRecord .Destination )
410447 if err != nil {
411448 return false , err
412449 } else if ! isDestinationAccountCreated {
@@ -700,7 +737,6 @@ func (h *CloseEmptyTimelockAccountFulfillmentHandler) OnFailure(ctx context.Cont
700737 // is dust in the account.
701738 //
702739 // todo: Implement auto-recovery when we know the account is empty
703- // todo: Do "something" to indicate the client needs to resign a new transaction
704740 return false , nil
705741}
706742
@@ -712,7 +748,7 @@ func (h *CloseEmptyTimelockAccountFulfillmentHandler) IsRevoked(ctx context.Cont
712748 return false , false , nil
713749}
714750
715- func isTokenAccountOnBlockchain (ctx context.Context , data code_data.Provider , address string ) (bool , error ) {
751+ func isAccountInitialized (ctx context.Context , data code_data.Provider , address string ) (bool , error ) {
716752 // Try our cache of Code timelock accounts
717753 timelockRecord , err := data .GetTimelockByVault (ctx , address )
718754 if err == timelock .ErrTimelockNotFound {
0 commit comments