Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ require (
github.com/onflow/flow-emulator v1.7.0
github.com/onflow/flow-evm-gateway v1.3.0
github.com/onflow/flow-go v0.43.0-dev-pebble.1.0.20250910132853-12699a150fd9
github.com/onflow/flow-go-sdk v1.8.1
github.com/onflow/flowkit/v2 v2.5.0
github.com/onflow/flow-go-sdk v1.8.3-0.20250917204435-cd9d4cf5f6af
github.com/onflow/flowkit/v2 v2.5.1
github.com/onflowser/flowser/v3 v3.2.1-0.20240131200229-7d4d22715f48
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
github.com/pkg/errors v0.9.1
Expand Down Expand Up @@ -291,7 +291,7 @@ require (
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect
google.golang.org/protobuf v1.36.7 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.4.1 // indirect
Expand Down
32 changes: 18 additions & 14 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -72,20 +72,20 @@ github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/aws/aws-sdk-go-v2 v1.38.1 h1:j7sc33amE74Rz0M/PoCpsZQ6OunLqys/m5antM0J+Z8=
github.com/aws/aws-sdk-go-v2 v1.38.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg=
github.com/aws/aws-sdk-go-v2 v1.39.0 h1:xm5WV/2L4emMRmMjHFykqiA4M/ra0DJVSWUkDyBjbg4=
github.com/aws/aws-sdk-go-v2 v1.39.0/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.5.1 h1:VGkV9KmhGqOQWnHyi4gLG98kE6OecT42fdrCGFWxJsc=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.5.1/go.mod h1:PLlnMiki//sGnCJiW+aVpvP/C8Kcm8mEj/IVm9+9qk4=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 h1:ueB2Te0NacDMnaC+68za9jLwkjzxGWm0KB5HTUHjLTI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4/go.mod h1:nLEfLnVMmLvyIG58/6gsSA03F1voKGaCfHV7+lR8S7s=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7 h1:mLgc5QIgOy26qyh5bvW+nDoAppxgn3J2WV3m9ewq7+8=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7/go.mod h1:wXb/eQnqt8mDQIQTTmcw58B5mYGxzLGZGK8PWNFZ0BA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.7.0 h1:HWsM0YQWX76V6MOp07YuTYacm8k7h69ObJuw7Nck+og=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.7.0/go.mod h1:LKb3cKNQIMh+itGnEpKGcnL/6OIjPZqrtYah1w5f+3o=
github.com/aws/aws-sdk-go-v2/service/s3 v1.15.0 h1:nPLfLPfglacc29Y949sDxpr3X/blaY40s3B85WT2yZU=
github.com/aws/aws-sdk-go-v2/service/s3 v1.15.0/go.mod h1:Iv2aJVtVSm/D22rFoX99cLG4q4uB7tppuCsulGe98k4=
github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw=
github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE=
github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
Expand Down Expand Up @@ -801,16 +801,20 @@ github.com/onflow/flow-ft/lib/go/templates v1.0.1 h1:FDYKAiGowABtoMNusLuRCILIZDt
github.com/onflow/flow-ft/lib/go/templates v1.0.1/go.mod h1:uQ8XFqmMK2jxyBSVrmyuwdWjTEb+6zGjRYotfDJ5pAE=
github.com/onflow/flow-go v0.43.0-dev-pebble.1.0.20250910132853-12699a150fd9 h1:LzgHQI7A8rINyfzKEF6x2gsrzx7zGBQpmgdHJjJtQqM=
github.com/onflow/flow-go v0.43.0-dev-pebble.1.0.20250910132853-12699a150fd9/go.mod h1:VkvpX4p4imUpPR+FhL0Qw7Qx32zQ/QOQRz2Vl2uu50Y=
github.com/onflow/flow-go-sdk v1.8.1 h1:BPp7p10RrpOdezQ3RJ+nheOqpalHlTB9bRocVkLsGNU=
github.com/onflow/flow-go-sdk v1.8.1/go.mod h1:w6bxCznDhJJCDybn1jCUAz3rEO4/7XY9EgWRFrj0zoo=
github.com/onflow/flow-go-sdk v1.8.2 h1:Jkoh0LdH4kIADFdHoHK31qHcH0QnCkAL+ybjAkBEbqc=
github.com/onflow/flow-go-sdk v1.8.2/go.mod h1:/5g1t+xgk1nFXz8ifk8O3aT4DqLwq5p3k2BhiW4ZX+U=
github.com/onflow/flow-go-sdk v1.8.3-0.20250917001858-5aa1f787eb8e h1:A4EVQWNo8bTlGV7O1e7AzKJ4WXVOTPryY2SHsCqVR3I=
github.com/onflow/flow-go-sdk v1.8.3-0.20250917001858-5aa1f787eb8e/go.mod h1:/5g1t+xgk1nFXz8ifk8O3aT4DqLwq5p3k2BhiW4ZX+U=
github.com/onflow/flow-go-sdk v1.8.3-0.20250917204435-cd9d4cf5f6af h1:C7pJVDcTEFqm65oW27WgpRhONOvPu4lFFWx9kGZKLgg=
github.com/onflow/flow-go-sdk v1.8.3-0.20250917204435-cd9d4cf5f6af/go.mod h1:/5g1t+xgk1nFXz8ifk8O3aT4DqLwq5p3k2BhiW4ZX+U=
github.com/onflow/flow-nft/lib/go/contracts v1.2.4 h1:gWJgSSgIGo0qWOqr90+khQ69VoYF9vNlqzF+Yh6YYy4=
github.com/onflow/flow-nft/lib/go/contracts v1.2.4/go.mod h1:eZ9VMMNfCq0ho6kV25xJn1kXeCfxnkhj3MwF3ed08gY=
github.com/onflow/flow-nft/lib/go/templates v1.2.1 h1:SAALMZPDw9Eb9p5kSLnmnFxjyig1MLiT4JUlLp0/bSE=
github.com/onflow/flow-nft/lib/go/templates v1.2.1/go.mod h1:W6hOWU0xltPqNpv9gQX8Pj8Jtf0OmRxc1XX2V0kzJaI=
github.com/onflow/flow/protobuf/go/flow v0.4.12 h1:nMJHVuz2iRQnzEwvmruCaMrQvm/dfdWtbKroi3o/42M=
github.com/onflow/flow/protobuf/go/flow v0.4.12/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk=
github.com/onflow/flowkit/v2 v2.5.0 h1:Y5eFuFHem8a4gNaZl+47Jr7qyBFkU0X+opUVUD72CcE=
github.com/onflow/flowkit/v2 v2.5.0/go.mod h1:wfWK0O8VxdCbAREo1Snnagt1l9GuqFdmyAQXeymzpiY=
github.com/onflow/flowkit/v2 v2.5.1 h1:02Y91zpwGDUhbGu+E4RVQ90ltaVcP6GLpNTBOEI5vaM=
github.com/onflow/flowkit/v2 v2.5.1/go.mod h1:59aJa3S+1MtDbb13wQF1igw7Nsvw5kQSUa0vBDGELmI=
github.com/onflow/go-ethereum v1.15.10 h1:blZBeOLJDOVWqKuhkkMh6S2PKQAJvdgbvOL9ZNggFcU=
github.com/onflow/go-ethereum v1.15.10/go.mod h1:t2nZJtwruVjA5u5yEK8InFzjImFLHrF7ak2bw3E4LDM=
github.com/onflow/nft-storefront/lib/go/contracts v1.0.0 h1:sxyWLqGm/p4EKT6DUlQESDG1ZNMN9GjPCm1gTq7NGfc=
Expand Down Expand Up @@ -1461,8 +1465,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
22 changes: 16 additions & 6 deletions internal/prompt/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,26 @@ func ApproveTransactionPrompt(tx *flow.Transaction, promptMsg string) bool {

for i, e := range tx.PayloadSignatures {
_, _ = fmt.Fprintf(writer, "\nPayload Signature %v:\n", i)
_, _ = fmt.Fprintf(writer, " Address\t%s\n", e.Address)
_, _ = fmt.Fprintf(writer, " Signature\t%x\n", e.Signature)
_, _ = fmt.Fprintf(writer, " Key Index\t%d\n", e.KeyIndex)
_, _ = fmt.Fprintf(writer, " Address\t\t%s\n", e.Address)
_, _ = fmt.Fprintf(writer, " Signature\t\t%x\n", e.Signature)
_, _ = fmt.Fprintf(writer, " Key Index\t\t%d\n", e.KeyIndex)
if len(e.ExtensionData) > 0 {
_, _ = fmt.Fprintf(writer, " Extension Data\t%x\n", e.ExtensionData)
} else {
_, _ = fmt.Fprintf(writer, " Extension Data\t None\n")
}
}

for i, e := range tx.EnvelopeSignatures {
_, _ = fmt.Fprintf(writer, "\nEnvelope Signature %v:\n", i)
_, _ = fmt.Fprintf(writer, " Address\t%s\n", e.Address)
_, _ = fmt.Fprintf(writer, " Signature\t%x\n", e.Signature)
_, _ = fmt.Fprintf(writer, " Key Index\t%d\n", e.KeyIndex)
_, _ = fmt.Fprintf(writer, " Address\t\t%s\n", e.Address)
_, _ = fmt.Fprintf(writer, " Signature\t\t%x\n", e.Signature)
_, _ = fmt.Fprintf(writer, " Key Index\t\t%d\n", e.KeyIndex)
if len(e.ExtensionData) > 0 {
_, _ = fmt.Fprintf(writer, " Extension Data\t%x\n", e.ExtensionData)
} else {
_, _ = fmt.Fprintf(writer, " Extension Data\t None\n")
}
}

if tx.Script != nil {
Expand Down
189 changes: 189 additions & 0 deletions internal/transactions/add-sig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/*
* Flow CLI
*
* Copyright Flow Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package transactions

import (
"context"
"encoding/hex"
"fmt"
"math"
"sort"
"strconv"
"strings"

flowsdk "github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go-sdk/crypto"
"github.com/spf13/cobra"

"github.com/onflow/flowkit/v2"
"github.com/onflow/flowkit/v2/accounts"
"github.com/onflow/flowkit/v2/config"
"github.com/onflow/flowkit/v2/output"
"github.com/onflow/flowkit/v2/transactions"

"github.com/onflow/flow-cli/internal/command"
"github.com/onflow/flow-cli/internal/prompt"
)

type flagsAddSig struct {
Include []string `default:"" flag:"include" info:"Fields to include in the output. Valid values: signatures, code, payload."`
KeyIndex string `default:"0" flag:"key-index" info:"Account key index"`
}

var addSigFlags = flagsAddSig{}

var addSigCommand = &command.Command{
Cmd: &cobra.Command{
Use: "add-sig <built transaction filename> <address> <signature>",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make the full use be add-signature and then have a shorthand for add-sig?

Short: "Add signature to a prebuilt transaction",
Example: "flow transactions add-sig ./built.rlp 99fa...25b",
Args: cobra.ExactArgs(3),
},
Flags: &addSigFlags,
RunS: addSig,
}

func addSig(
args []string,
globalFlags command.GlobalFlags,
_ output.Logger,
flow flowkit.Services,
state *flowkit.State,
) (command.Result, error) {
var payload []byte
var err error
filename := args[0]
payload, err = state.ReadFile(filename)

if err != nil {
return nil, fmt.Errorf("failed to read partial transaction from %s: %v", filename, err)
}

address := flowsdk.HexToAddress(args[1])

sig, err := hex.DecodeString(strings.ReplaceAll(args[2], "0x", ""))
if err != nil {
return nil, fmt.Errorf("invalid message signature: %w", err)
}

index, err := parseKeyIndex(addSigFlags.KeyIndex)
if err != nil {
return nil, err
}

var signed *transactions.Transaction
var signers []*accounts.Account
tx, err := transactions.NewFromPayload(payload)
if err != nil {
return nil, err
}

// validate all signers
signerAccount := &accounts.Account{
Address: address,
Key: &dummyKey{index: index, signer: crypto.NewAddSignatureSigner(sig, nil)},
}

//payer signs last
sort.SliceStable(signers, func(i, j int) bool {
return signers[i].Address.String() != tx.FlowTransaction().Payer.Hex()
})

if !globalFlags.Yes && !prompt.ApproveTransactionForSigningPrompt(tx.FlowTransaction()) {
return nil, fmt.Errorf("transaction was not approved for signing")
}

signed, err = flow.SignTransactionPayload(context.Background(), signerAccount, payload)
if err != nil {
return nil, err
}

return &transactionResult{
tx: signed.FlowTransaction(),
include: addSigFlags.Include,
network: flow.Network().Name,
}, nil
}

type dummyKey struct {
keyType config.KeyType
index uint32
sigAlgo crypto.SignatureAlgorithm
hashAlgo crypto.HashAlgorithm
signer crypto.Signer
}

var _ accounts.Key = &dummyKey{}

func (a *dummyKey) Type() config.KeyType {
return a.keyType
}

func (a *dummyKey) SigAlgo() crypto.SignatureAlgorithm {
if a.sigAlgo == crypto.UnknownSignatureAlgorithm {
return crypto.ECDSA_P256 // default value
}
return a.sigAlgo
}

func (a *dummyKey) HashAlgo() crypto.HashAlgorithm {
if a.hashAlgo == crypto.UnknownHashAlgorithm {
return crypto.SHA3_256 // default value
}
return a.hashAlgo
}

func (a *dummyKey) Index() uint32 {
return a.index // default to 0
}

func (a *dummyKey) Validate() error {
return nil
}
func (a *dummyKey) Signer(ctx context.Context) (crypto.Signer, error) {
return a.signer, nil
}
func (a *dummyKey) ToConfig() config.AccountKey {
return config.AccountKey{
Type: a.keyType,
Index: a.index,
SigAlgo: a.sigAlgo,
HashAlgo: a.hashAlgo,
Mnemonic: "",
DerivationPath: "",
}
}
func (a *dummyKey) PrivateKey() (*crypto.PrivateKey, error) {
return nil, fmt.Errorf("This key type does not support private key retrieval")
}

func parseKeyIndex(value string) (uint32, error) {
v, err := strconv.Atoi(value)
if err != nil {
return 0, fmt.Errorf("invalid index, must be a number")
}
if v < 0 {
return 0, fmt.Errorf("invalid index, must be positive")
}
if v > math.MaxUint32 {
return 0, fmt.Errorf("invalid index, must be less than %d", math.MaxUint32)
}

return uint32(v), nil
}
7 changes: 7 additions & 0 deletions internal/transactions/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func init() {
getCommand.AddToParent(Cmd)
sendCommand.AddToParent(Cmd)
signCommand.AddToParent(Cmd)
addSigCommand.AddToParent(Cmd)
buildCommand.AddToParent(Cmd)
sendSignedCommand.AddToParent(Cmd)
getSystemCommand.AddToParent(Cmd)
Expand Down Expand Up @@ -171,6 +172,9 @@ func (r *transactionResult) String() string {
_, _ = fmt.Fprintf(writer, " Address\t%s\n", e.Address)
_, _ = fmt.Fprintf(writer, " Signature\t%x\n", e.Signature)
_, _ = fmt.Fprintf(writer, " Key Index\t%d\n", e.KeyIndex)
if len(e.ExtensionData) > 0 {
_, _ = fmt.Fprintf(writer, " Extension Data\t%x\n", e.ExtensionData)
}
} else {
_, _ = fmt.Fprintf(writer, "\nPayload Signature %v: %s", i, e.Address)
}
Expand All @@ -182,6 +186,9 @@ func (r *transactionResult) String() string {
_, _ = fmt.Fprintf(writer, " Address\t%s\n", e.Address)
_, _ = fmt.Fprintf(writer, " Signature\t%x\n", e.Signature)
_, _ = fmt.Fprintf(writer, " Key Index\t%d\n", e.KeyIndex)
if len(e.ExtensionData) > 0 {
_, _ = fmt.Fprintf(writer, " Extension Data\t%x\n", e.ExtensionData)
}
} else {
_, _ = fmt.Fprintf(writer, "\nEnvelope Signature %v: %s", i, e.Address)
}
Expand Down
Loading