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 send 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,6 +402,8 @@ func (h *NoPrivacyTransferWithAuthorityFulfillmentHandler) MakeOnDemandTransacti
368402 sourceMemory ,
369403 sourceIndex ,
370404
405+ isCreateOnSend ,
406+ destinationOwnerAccount ,
371407 destinationTokenAccount ,
372408 * actionRecord .Quantity ,
373409 )
@@ -397,7 +433,7 @@ func (h *NoPrivacyWithdrawFulfillmentHandler) CanSubmitToBlockchain(ctx context.
397433
398434 // The source user account is a Code account, so we must validate it exists on
399435 // the blockchain prior to sending funds from it.
400- isSourceAccountCreated , err := isTokenAccountOnBlockchain (ctx , h .data , fulfillmentRecord .Source )
436+ isSourceAccountCreated , err := isAccountInitialized (ctx , h .data , fulfillmentRecord .Source )
401437 if err != nil {
402438 return false , err
403439 } else if ! isSourceAccountCreated {
@@ -406,7 +442,7 @@ func (h *NoPrivacyWithdrawFulfillmentHandler) CanSubmitToBlockchain(ctx context.
406442
407443 // The destination user account might be a Code account or external wallet, so we
408444 // must validate it exists on the blockchain prior to send funds to it.
409- isDestinationAccountCreated , err := isTokenAccountOnBlockchain (ctx , h .data , * fulfillmentRecord .Destination )
445+ isDestinationAccountCreated , err := isAccountInitialized (ctx , h .data , * fulfillmentRecord .Destination )
410446 if err != nil {
411447 return false , err
412448 } else if ! isDestinationAccountCreated {
@@ -700,7 +736,6 @@ func (h *CloseEmptyTimelockAccountFulfillmentHandler) OnFailure(ctx context.Cont
700736 // is dust in the account.
701737 //
702738 // 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
704739 return false , nil
705740}
706741
@@ -712,7 +747,7 @@ func (h *CloseEmptyTimelockAccountFulfillmentHandler) IsRevoked(ctx context.Cont
712747 return false , false , nil
713748}
714749
715- func isTokenAccountOnBlockchain (ctx context.Context , data code_data.Provider , address string ) (bool , error ) {
750+ func isAccountInitialized (ctx context.Context , data code_data.Provider , address string ) (bool , error ) {
716751 // Try our cache of Code timelock accounts
717752 timelockRecord , err := data .GetTimelockByVault (ctx , address )
718753 if err == timelock .ErrTimelockNotFound {
0 commit comments