7
7
"github.com/mr-tron/base58"
8
8
9
9
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"
10
11
indexerpb "github.com/code-payments/code-vm-indexer/generated/indexer/v1"
11
12
12
13
"github.com/code-payments/code-server/pkg/code/common"
@@ -209,20 +210,35 @@ func (h *NoPrivacyTransferWithAuthorityFulfillmentHandler) CanSubmitToBlockchain
209
210
210
211
// The source user account is a Code account, so we must validate it exists on
211
212
// 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 )
213
214
if err != nil {
214
215
return false , err
215
216
} else if ! isSourceAccountCreated {
216
217
return false , nil
217
218
}
218
219
219
220
// 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 )
222
224
if err != nil {
223
225
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
+ }
226
242
}
227
243
228
244
// Check whether there's an earlier fulfillment that should be scheduled first
@@ -269,17 +285,17 @@ func (h *NoPrivacyTransferWithAuthorityFulfillmentHandler) SupportsOnDemandTrans
269
285
}
270
286
271
287
func (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 )
273
289
if err != nil {
274
290
return nil , err
275
291
}
276
292
277
- virtualNonce , err := common . NewAccountFromPublicKeyString (* fulfillmentRecord .VirtualNonce )
293
+ virtualSignatureBytes , err := base58 . Decode (* fulfillmentRecord .VirtualSignature )
278
294
if err != nil {
279
295
return nil , err
280
296
}
281
297
282
- actionRecord , err := h . data . GetActionById ( ctx , fulfillmentRecord .Intent , fulfillmentRecord . ActionId )
298
+ virtualNonce , err := common . NewAccountFromPublicKeyString ( * fulfillmentRecord .VirtualNonce )
283
299
if err != nil {
284
300
return nil , err
285
301
}
@@ -354,6 +370,24 @@ func (h *NoPrivacyTransferWithAuthorityFulfillmentHandler) MakeOnDemandTransacti
354
370
* actionRecord .Quantity ,
355
371
)
356
372
} 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
+
357
391
txn , makeTxnErr = transaction_util .MakeExternalTransferWithAuthorityTransaction (
358
392
selectedNonce .Account ,
359
393
selectedNonce .Blockhash ,
@@ -368,7 +402,10 @@ func (h *NoPrivacyTransferWithAuthorityFulfillmentHandler) MakeOnDemandTransacti
368
402
sourceMemory ,
369
403
sourceIndex ,
370
404
405
+ isCreateOnSend ,
406
+ destinationOwnerAccount ,
371
407
destinationTokenAccount ,
408
+
372
409
* actionRecord .Quantity ,
373
410
)
374
411
}
@@ -397,7 +434,7 @@ func (h *NoPrivacyWithdrawFulfillmentHandler) CanSubmitToBlockchain(ctx context.
397
434
398
435
// The source user account is a Code account, so we must validate it exists on
399
436
// 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 )
401
438
if err != nil {
402
439
return false , err
403
440
} else if ! isSourceAccountCreated {
@@ -406,7 +443,7 @@ func (h *NoPrivacyWithdrawFulfillmentHandler) CanSubmitToBlockchain(ctx context.
406
443
407
444
// The destination user account might be a Code account or external wallet, so we
408
445
// 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 )
410
447
if err != nil {
411
448
return false , err
412
449
} else if ! isDestinationAccountCreated {
@@ -700,7 +737,6 @@ func (h *CloseEmptyTimelockAccountFulfillmentHandler) OnFailure(ctx context.Cont
700
737
// is dust in the account.
701
738
//
702
739
// 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
704
740
return false , nil
705
741
}
706
742
@@ -712,7 +748,7 @@ func (h *CloseEmptyTimelockAccountFulfillmentHandler) IsRevoked(ctx context.Cont
712
748
return false , false , nil
713
749
}
714
750
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 ) {
716
752
// Try our cache of Code timelock accounts
717
753
timelockRecord , err := data .GetTimelockByVault (ctx , address )
718
754
if err == timelock .ErrTimelockNotFound {
0 commit comments