Skip to content

Commit aa19e64

Browse files
committed
Add cancel tx
1 parent c8280cb commit aa19e64

File tree

2 files changed

+150
-20
lines changed

2 files changed

+150
-20
lines changed

internal/schedule/cancel.go

Lines changed: 120 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,20 @@
1919
package schedule
2020

2121
import (
22+
"context"
2223
"fmt"
24+
"strconv"
2325

26+
"github.com/onflow/cadence"
27+
flowsdk "github.com/onflow/flow-go-sdk"
2428
"github.com/spf13/cobra"
2529

2630
"github.com/onflow/flowkit/v2"
31+
"github.com/onflow/flowkit/v2/accounts"
2732
"github.com/onflow/flowkit/v2/output"
33+
"github.com/onflow/flowkit/v2/transactions"
2834

35+
"github.com/onflow/flow-cli/common/branding"
2936
"github.com/onflow/flow-cli/internal/command"
3037
"github.com/onflow/flow-cli/internal/util"
3138
)
@@ -71,42 +78,141 @@ func cancelRun(
7178
return nil, fmt.Errorf("transaction ID is required as an argument")
7279
}
7380

74-
transactionID := args[0]
81+
transactionIDStr := args[0]
82+
83+
// Parse transaction ID as UInt64
84+
transactionID, err := strconv.ParseUint(transactionIDStr, 10, 64)
85+
if err != nil {
86+
return nil, fmt.Errorf("invalid transaction ID: %w", err)
87+
}
7588

7689
signer, err := util.GetSignerAccount(state, cancelFlags.Signer)
7790
if err != nil {
7891
return nil, err
7992
}
8093

81-
logger.Info(fmt.Sprintf("Network: %s", globalFlags.Network))
82-
logger.Info(fmt.Sprintf("Signer: %s (%s)", cancelFlags.Signer, signer.Address.String()))
83-
logger.Info(fmt.Sprintf("Transaction ID: %s", transactionID))
84-
logger.Info("Canceling scheduled transaction...")
94+
chainID, err := util.NetworkToChainID(globalFlags.Network)
95+
if err != nil {
96+
return nil, err
97+
}
98+
99+
if chainID == flowsdk.Mainnet {
100+
return nil, fmt.Errorf("transaction scheduling is not yet supported on mainnet")
101+
}
102+
103+
schedulerUtilsAddress, err := getContractAddress(FlowTransactionSchedulerUtils, chainID)
104+
if err != nil {
105+
return nil, err
106+
}
107+
108+
flowTokenAddress, err := getContractAddress(FlowToken, chainID)
109+
if err != nil {
110+
return nil, err
111+
}
112+
113+
fungibleTokenAddress, err := getContractAddress(FungibleToken, chainID)
114+
if err != nil {
115+
return nil, err
116+
}
117+
118+
networkStr := branding.GrayStyle.Render(globalFlags.Network)
119+
addressStr := branding.PurpleStyle.Render(signer.Address.HexWithPrefix())
120+
signerStr := branding.GrayStyle.Render(cancelFlags.Signer)
121+
txIDStr := branding.PurpleStyle.Render(transactionIDStr)
122+
123+
logger.Info("Canceling scheduled transaction")
124+
logger.Info("")
125+
logger.Info(fmt.Sprintf("🌐 Network: %s", networkStr))
126+
logger.Info(fmt.Sprintf("📝 Signer: %s (%s)", signerStr, addressStr))
127+
logger.Info(fmt.Sprintf("🔍 Transaction ID: %s", txIDStr))
128+
logger.Info("")
129+
130+
// Build transaction code
131+
cancelTx := fmt.Sprintf(`import FlowTransactionSchedulerUtils from %s
132+
import FlowToken from %s
133+
import FungibleToken from %s
134+
135+
transaction(transactionId: UInt64) {
136+
let manager: auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}
137+
let tokenReceiver: &{FungibleToken.Receiver}
138+
139+
prepare(signer: auth(BorrowValue) &Account) {
140+
// 1. Borrow Manager reference
141+
self.manager = signer.storage.borrow<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}>(
142+
from: FlowTransactionSchedulerUtils.managerStoragePath
143+
) ?? panic("Could not borrow Manager. Please ensure you have a Manager set up.")
144+
145+
// Verify transaction exists in manager
146+
assert(
147+
self.manager.getTransactionIDs().contains(transactionId),
148+
message: "Transaction with ID ".concat(transactionId.toString()).concat(" not found in manager")
149+
)
150+
151+
// 2. Get FlowToken receiver to deposit refunds
152+
self.tokenReceiver = signer.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)
153+
.borrow()
154+
?? panic("Could not borrow FlowToken receiver")
155+
}
156+
157+
execute {
158+
// Cancel the transaction and receive refunded fees
159+
let refundVault <- self.manager.cancel(id: transactionId)
160+
161+
// Deposit refunded fees back to the account
162+
self.tokenReceiver.deposit(from: <-refundVault)
163+
}
164+
}`, schedulerUtilsAddress, flowTokenAddress, fungibleTokenAddress)
165+
166+
_, txResult, err := flow.SendTransaction(
167+
context.Background(),
168+
transactions.AccountRoles{
169+
Proposer: *signer,
170+
Authorizers: []accounts.Account{*signer},
171+
Payer: *signer,
172+
},
173+
flowkit.Script{
174+
Code: []byte(cancelTx),
175+
Args: []cadence.Value{cadence.NewUInt64(transactionID)},
176+
},
177+
1000,
178+
)
179+
180+
if err != nil {
181+
return nil, fmt.Errorf("failed to cancel scheduled transaction: %w", err)
182+
}
183+
184+
if txResult.Error != nil {
185+
return nil, fmt.Errorf("cancel transaction failed: %s", txResult.Error.Error())
186+
}
85187

86-
// TODO: Implement cancel logic for scheduled transaction
188+
logger.Info("")
189+
successIcon := branding.GreenStyle.Render("✅")
190+
successMsg := branding.GreenStyle.Render("Scheduled transaction canceled successfully")
191+
logger.Info(fmt.Sprintf("%s %s", successIcon, successMsg))
87192

88193
return &cancelResult{
89-
success: true,
90-
message: fmt.Sprintf("Scheduled transaction %s canceled successfully", transactionID),
194+
success: true,
195+
transactionID: transactionIDStr,
91196
}, nil
92197
}
93198

94199
type cancelResult struct {
95-
success bool
96-
message string
200+
success bool
201+
transactionID string
97202
}
98203

99204
func (r *cancelResult) JSON() any {
100205
return map[string]any{
101-
"success": r.success,
102-
"message": r.message,
206+
"success": r.success,
207+
"transactionID": r.transactionID,
208+
"message": "Scheduled transaction canceled successfully",
103209
}
104210
}
105211

106212
func (r *cancelResult) String() string {
107-
return r.message
213+
return ""
108214
}
109215

110216
func (r *cancelResult) Oneliner() string {
111-
return r.message
217+
return fmt.Sprintf("Scheduled transaction %s canceled successfully", r.transactionID)
112218
}

internal/schedule/contracts.go

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,18 @@ import (
2222
"fmt"
2323

2424
flowsdk "github.com/onflow/flow-go-sdk"
25+
"github.com/onflow/flow-go/fvm/systemcontracts"
26+
flowGo "github.com/onflow/flow-go/model/flow"
2527
)
2628

2729
// ContractName represents a scheduler-related contract name
2830
type ContractName string
2931

3032
const (
31-
// FlowTransactionSchedulerUtils contract provides utility functions for transaction scheduling
3233
FlowTransactionSchedulerUtils ContractName = "FlowTransactionSchedulerUtils"
33-
// FlowTransactionScheduler contract handles the core transaction scheduling logic
34-
FlowTransactionScheduler ContractName = "FlowTransactionScheduler"
34+
FlowTransactionScheduler ContractName = "FlowTransactionScheduler"
35+
FlowToken ContractName = "FlowToken"
36+
FungibleToken ContractName = "FungibleToken"
3537
)
3638

3739
// contractAddresses maps contract names to their addresses on different networks
@@ -48,12 +50,34 @@ var contractAddresses = map[ContractName]map[flowsdk.ChainID]string{
4850

4951
// getContractAddress returns the contract address for the given contract name and network
5052
func getContractAddress(contract ContractName, chainID flowsdk.ChainID) (string, error) {
51-
// Check if mainnet
52-
if chainID == flowsdk.Mainnet {
53+
// Check if mainnet for scheduling contracts
54+
if chainID == flowsdk.Mainnet && (contract == FlowTransactionSchedulerUtils || contract == FlowTransactionScheduler) {
5355
return "", fmt.Errorf("transaction scheduling is not yet supported on mainnet")
5456
}
5557

56-
// Look up the contract address
58+
// Handle system contracts using the systemcontracts library
59+
if contract == FlowToken || contract == FungibleToken {
60+
var flowGoChainID flowGo.ChainID
61+
switch chainID {
62+
case flowsdk.Emulator:
63+
flowGoChainID = flowGo.Emulator
64+
case flowsdk.Testnet:
65+
flowGoChainID = flowGo.Testnet
66+
case flowsdk.Mainnet:
67+
flowGoChainID = flowGo.Mainnet
68+
default:
69+
return "", fmt.Errorf("unsupported chain ID: %s", chainID)
70+
}
71+
72+
systemContracts := systemcontracts.SystemContractsForChain(flowGoChainID)
73+
switch contract {
74+
case FlowToken:
75+
return systemContracts.FlowToken.Address.HexWithPrefix(), nil
76+
case FungibleToken:
77+
return systemContracts.FungibleToken.Address.HexWithPrefix(), nil
78+
}
79+
}
80+
5781
networkAddresses, contractExists := contractAddresses[contract]
5882
if !contractExists {
5983
return "", fmt.Errorf("unknown contract: %s", contract)

0 commit comments

Comments
 (0)