package task import ( "context" "encoding/json" "math/big" "time" "github.com/bsm/redislock" "github.com/celo-org/celo-blockchain/common/hexutil" "github.com/grassrootseconomics/celoutils" "github.com/grassrootseconomics/cic-custodial/internal/custodial" "github.com/grassrootseconomics/cic-custodial/internal/store" "github.com/grassrootseconomics/cic-custodial/internal/tasker" "github.com/grassrootseconomics/cic-custodial/pkg/enum" "github.com/grassrootseconomics/w3-celo-patch/module/eth" "github.com/hibiken/asynq" ) type TransferAuthPayload struct { Amount uint64 `json:"amount"` Authorizer string `json:"authorizer"` AuthorizedAddress string `json:"authorizedAddress"` TrackingId string `json:"trackingId"` VoucherAddress string `json:"voucherAddress"` } func SignTransferAuthorizationProcessor(cu *custodial.Custodial) func(context.Context, *asynq.Task) error { return func(ctx context.Context, t *asynq.Task) error { var ( err error networkBalance big.Int payload TransferAuthPayload ) if err := json.Unmarshal(t.Payload(), &payload); err != nil { return err } lock, err := cu.LockProvider.Obtain( ctx, lockPrefix+payload.Authorizer, lockTimeout, &redislock.Options{ RetryStrategy: lockRetry(), }, ) if err != nil { return err } defer lock.Release(ctx) key, err := cu.Store.LoadPrivateKey(ctx, payload.Authorizer) if err != nil { return err } nonce, err := cu.Noncestore.Acquire(ctx, payload.Authorizer) if err != nil { return err } defer func() { if err != nil { if nErr := cu.Noncestore.Return(ctx, payload.Authorizer); nErr != nil { err = nErr } } }() input, err := cu.Abis[custodial.Approve].EncodeArgs( celoutils.HexToAddress(payload.AuthorizedAddress), new(big.Int).SetUint64(payload.Amount), ) if err != nil { return err } builtTx, err := cu.CeloProvider.SignContractExecutionTx( key, celoutils.ContractExecutionTxOpts{ ContractAddress: celoutils.HexToAddress(payload.VoucherAddress), InputData: input, GasFeeCap: celoutils.SafeGasFeeCap, GasTipCap: celoutils.SafeGasTipCap, GasLimit: uint64(celoutils.SafeGasLimit), Nonce: nonce, }, ) if err != nil { return err } rawTx, err := builtTx.MarshalBinary() if err != nil { return err } id, err := cu.Store.CreateOtx(ctx, store.Otx{ TrackingId: payload.TrackingId, Type: enum.TRANSFER_AUTH, RawTx: hexutil.Encode(rawTx), TxHash: builtTx.Hash().Hex(), From: cu.SystemPublicKey, Data: hexutil.Encode(builtTx.Data()), GasPrice: builtTx.GasPrice(), GasLimit: builtTx.Gas(), TransferValue: 0, Nonce: builtTx.Nonce(), }) if err != nil { return err } if err := cu.CeloProvider.Client.CallCtx( ctx, eth.Balance(celoutils.HexToAddress(payload.Authorizer), nil).Returns(&networkBalance), ); err != nil { return err } disptachJobPayload, err := json.Marshal(TxPayload{ OtxId: id, Tx: builtTx, }) if err != nil { return err } _, err = cu.TaskerClient.CreateTask( ctx, tasker.DispatchTxTask, tasker.HighPriority, &tasker.Task{ Payload: disptachJobPayload, }, ) if err != nil { return err } // Auto-revoke every session (15 min) // Check if already a revoke request if payload.Amount > 0 { taskPayload, err := json.Marshal(TransferAuthPayload{ TrackingId: payload.TrackingId, Amount: 0, Authorizer: payload.Authorizer, AuthorizedAddress: payload.AuthorizedAddress, VoucherAddress: payload.VoucherAddress, }) if err != nil { return err } _, err = cu.TaskerClient.CreateTask( ctx, tasker.SignTransferTaskAuth, tasker.DefaultPriority, &tasker.Task{ Payload: taskPayload, }, asynq.ProcessIn(time.Minute*15), ) if err != nil { return err } } gasRefillPayload, err := json.Marshal(AccountPayload{ PublicKey: payload.Authorizer, TrackingId: payload.TrackingId, }) if err != nil { return err } if !balanceCheck(networkBalance) { if err := cu.Store.GasLock(ctx, payload.Authorizer); err != nil { return err } _, err = cu.TaskerClient.CreateTask( ctx, tasker.AccountRefillGasTask, tasker.DefaultPriority, &tasker.Task{ Payload: gasRefillPayload, }, ) if err != nil { return err } } return nil } }