diff --git a/apps/bitcoin/account_test.go b/apps/bitcoin/account_test.go deleted file mode 100644 index 682e4c7b..00000000 --- a/apps/bitcoin/account_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package bitcoin - -import ( - "encoding/base64" - "encoding/binary" - "encoding/hex" - "testing" - "time" - - "github.com/MixinNetwork/multi-party-sig/pkg/ecdsa" - "github.com/MixinNetwork/multi-party-sig/pkg/math/curve" - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/btcutil/hdkeychain" - "github.com/btcsuite/btcd/chaincfg" - "github.com/stretchr/testify/require" -) - -func TestBitcoinCLI(t *testing.T) { - require := require.New(t) - - msg := "mixin safe" - pub := "02221eebc257e4789e3893292e78c19d5feb7788397d511afb3ffb14561ade500a" - sig := "H3RKBE7bK/BoKoupbB7BC8fKeesHst3tLhfhNSkAPZ8XZuB3nE8YJRPx/6ZPI7PN9fsq2PrnfpETCEoLA8PHAfY=" - - err := VerifyHolderKey(pub) - require.Nil(err) - - s, err := base64.StdEncoding.DecodeString(sig) - require.Nil(err) - es, err := ecdsa.ParseSignature(curve.Secp256k1{}, s) - require.Nil(err) - - s = es.SerializeDER() - - messageHash := HashMessageForSignature(msg, ChainBitcoin) - err = VerifySignatureDER(pub, messageHash, s) - require.Nil(err) - - // bitcoin-cli --rpcwallet=holder listdescriptors true - extPriv, err := hdkeychain.NewKeyFromString("xprv9s21ZrQH143K4NMg6FdKfSHPJN9W642rDck71dJ2j1N4SFePwJjkNm1xU3FCHUEjR9M4ZLCDKC6DonAyNhwg6NNhCoJFojRBeFzPwcQXTMS") - require.Nil(err) - require.True(extPriv.IsPrivate()) - require.True(extPriv.IsForNet(&chaincfg.MainNetParams)) - require.Equal("xprv9s21ZrQH143K4NMg6FdKfSHPJN9W642rDck71dJ2j1N4SFePwJjkNm1xU3FCHUEjR9M4ZLCDKC6DonAyNhwg6NNhCoJFojRBeFzPwcQXTMS", extPriv.String()) - require.Equal(NetConfig(ChainBitcoin).HDPrivateKeyID[:], extPriv.Version()) - require.Equal(byte(0x0), extPriv.Depth()) - require.Equal(uint32(0x0), extPriv.ParentFingerprint()) - require.Equal(uint32(0x0), extPriv.ChildIndex()) - require.Equal([]byte{0xe8, 0xe, 0x10, 0x47, 0x2e, 0xce, 0x36, 0x5a, 0x31, 0x89, 0xbc, 0x28, 0x59, 0x46, 0xc8, 0xce, 0x5a, 0x1d, 0xe0, 0x1c, 0x48, 0x5a, 0x90, 0xc7, 0x93, 0x1f, 0xc3, 0xe, 0x10, 0x47, 0xe4, 0x97}, extPriv.ChainCode()) - - // bitcoin-cli --rpcwallet=holder getaddressinfo 1DdhSdxiLepsH2YuiVxt8n85UHmWp4qjUt - extPriv, _ = extPriv.Derive(0x80000000 + 44) - extPriv, _ = extPriv.Derive(0x80000000) - extPub, err := extPriv.Neuter() - require.Nil(err) - ecPub, _ := extPub.ECPubKey() - parentFP := btcutil.Hash160(ecPub.SerializeCompressed())[:4] - require.Equal(uint32(0x1987f5fc), binary.BigEndian.Uint32(parentFP)) - - extPriv, _ = extPriv.Derive(0x80000000) - extPub, err = extPriv.Neuter() - require.Nil(err) - require.False(extPub.IsPrivate()) - require.True(extPub.IsForNet(&chaincfg.MainNetParams)) - require.Equal(NetConfig(ChainBitcoin).HDPublicKeyID[:], extPub.Version()) - require.Equal(byte(0x3), extPub.Depth()) - require.Equal(binary.BigEndian.Uint32(parentFP), extPub.ParentFingerprint()) - require.Equal(uint32(0x80000000), extPub.ChildIndex()) - require.Equal("xpub6Bqeq5d3McUGMHv6PhMPhCCnJt1JSgRJSYHP9q9uLLUnVh9AESmn8NHAsp5NneVg5orAc6EcTrEfMVTTrei6k3J5YPn8MgmN39aiqmD6wjH", extPub.String()) - - extPriv, _ = extPriv.Derive(0) - extPriv, _ = extPriv.Derive(0) - apkh, _ := extPriv.Address(&chaincfg.MainNetParams) - require.Equal("1DdhSdxiLepsH2YuiVxt8n85UHmWp4qjUt", apkh.EncodeAddress()) - ecPub, _ = extPriv.ECPubKey() - require.Equal("03911c1ef3960be7304596cfa6073b1d65ad43b421a4c272142cc7a8369b510c56", hex.EncodeToString(ecPub.SerializeCompressed())) - ecPriv, _ := extPriv.ECPrivKey() - require.Equal("52250bb9b9edc5d54466182778a6470a5ee34033c215c92dd250b9c2ce543556", hex.EncodeToString(ecPriv.Serialize())) -} - -func TestBitcoinAddress(t *testing.T) { - require := require.New(t) - script, err := ParseAddress("bc1q7wqpsk0ckquckd7v0e38uqkscjh7v0ncelqpz459hueet5uknamqrlgp2d", ChainBitcoin) - require.Nil(err) - require.Equal("0020f3801859f8b0398b37cc7e627e02d0c4afe63e78cfc0115685bf3395d3969f76", hex.EncodeToString(script)) - script, err = ParseAddress("bc1q7wqpsk0ckquckd7v0e38uqkscjh7v0ncelqpz459hueet5uknamqrlgp2d", ChainLitecoin) - require.NotNil(err) - require.Equal("", hex.EncodeToString(script)) - - script, err = ParseAddress("ltc1qhlq0h89m6n0a099kr55qssaz2u82xj5u66taffekch2dfh6vf7escf4l0k", ChainLitecoin) - require.Nil(err) - require.Equal("0020bfc0fb9cbbd4dfd794b61d280843a2570ea34a9cd697d4a736c5d4d4df4c4fb3", hex.EncodeToString(script)) - script, err = ParseAddress("ltc1qhlq0h89m6n0a099kr55qssaz2u82xj5u66taffekch2dfh6vf7escf4l0k", ChainBitcoin) - require.NotNil(err) - require.Equal("", hex.EncodeToString(script)) -} - -func TestBitcoinScriptAddress(t *testing.T) { - require := require.New(t) - lock := time.Hour * 24 * 90 - holder := "03911c1ef3960be7304596cfa6073b1d65ad43b421a4c272142cc7a8369b510c56" - signer := "028a010d50f3ba6f17ee313f55a1d06412674f2064616b4642f4eee3cb471eeef5" - observer := "028628daebf3cb6e902dfb6e605edb28d0d9717526fce2d9e1a66a7e4a58ad6f65" - - wsa, err := BuildWitnessScriptAccount(holder, signer, observer, lock, ChainBitcoin) - require.Nil(err) - require.Equal("bc1q2nhm0clwt7qcmnpntetjlzf0tflp2h0zvkczql4v9nmydnt7xm6swx2nnv", wsa.Address) - require.Equal("2103911c1ef3960be7304596cfa6073b1d65ad43b421a4c272142cc7a8369b510c56ac7c21028a010d50f3ba6f17ee313f55a1d06412674f2064616b4642f4eee3cb471eeef5ac937c82926321028628daebf3cb6e902dfb6e605edb28d0d9717526fce2d9e1a66a7e4a58ad6f65ad02a032b29268935287", hex.EncodeToString(wsa.Script)) -} - -func TestBitcoinSignature(t *testing.T) { - require := require.New(t) - sig, _ := hex.DecodeString("304402206c9adbfea684f9dca42700db018a6aaebbee1f679f553e871351031ccdbff3510220064eeed0c51e0a018b4c275e6585fd81d686c106be1708507a1bd4813affb7a881") - sig, err := CanonicalSignatureDER(sig) - require.Nil(err) - require.Equal("304402206c9adbfea684f9dca42700db018a6aaebbee1f679f553e871351031ccdbff3510220064eeed0c51e0a018b4c275e6585fd81d686c106be1708507a1bd4813affb7a8", hex.EncodeToString(sig)) - - sig, _ = hex.DecodeString("304402206c9adbfea684f9dca42700db018a6aaebbee1f679f553e871351031ccdbff3510220064eeed0c51e0a018b4c275e6585fd81d686c106be1708507a1bd4813affb7a88181") - sig, err = CanonicalSignatureDER(sig) - require.Nil(err) - require.Equal("304402206c9adbfea684f9dca42700db018a6aaebbee1f679f553e871351031ccdbff3510220064eeed0c51e0a018b4c275e6585fd81d686c106be1708507a1bd4813affb7a8", hex.EncodeToString(sig)) - - sig, _ = hex.DecodeString("304402206c9adbfea684f9dca42700db018a6aaebbee1f679f553e871351031ccdbff3510220064eeed0c51e0a018b4c275e6585fd81d686c106be1708507a1bd4813affb7a8") - sig, err = CanonicalSignatureDER(sig) - require.Nil(err) - require.Equal("304402206c9adbfea684f9dca42700db018a6aaebbee1f679f553e871351031ccdbff3510220064eeed0c51e0a018b4c275e6585fd81d686c106be1708507a1bd4813affb7a8", hex.EncodeToString(sig)) - - sig, _ = hex.DecodeString("304402206c9adbfea684f9dca42700db018a6aaebbee1f679f553e871351031ccdbff3510220064eeed0c51e0a018b4c275e6585fd81d686c106be1708507a1bd4813affb7") - sig, err = CanonicalSignatureDER(sig) - require.NotNil(err) - require.Nil(sig) -} diff --git a/apps/solana/account.go b/apps/solana/account.go new file mode 100644 index 00000000..34804607 --- /dev/null +++ b/apps/solana/account.go @@ -0,0 +1,50 @@ +package solana + +import ( + "encoding/hex" + "fmt" + + solana "github.com/gagliardetto/solana-go" + "github.com/mr-tron/base58" +) + +func MPK(public string) solana.PublicKey { + key, err := PublicKeyFromString(public) + if err != nil { + panic(err) + } + + return key +} + +func PublicKeyFromString(public string) (solana.PublicKey, error) { + b, err := base58.Decode(public) + if err != nil || len(b) != solana.PublicKeyLength { + b, err = hex.DecodeString(public) + } + + if len(b) != solana.PublicKeyLength { + return solana.PublicKey{}, fmt.Errorf("invalid public key length %d", len(b)) + } + + return solana.PublicKeyFromBytes(b), nil +} + +func VerifyHolderKey(public string) error { + _, err := PublicKeyFromString(public) + return err +} + +func VerifyMessageSignature(public string, msg, signature []byte) error { + pub, err := PublicKeyFromString(public) + if err != nil { + return err + } + + sig := solana.SignatureFromBytes(signature) + if pub.Verify(msg, sig) { + return nil + } + + return fmt.Errorf("solana.VerifyMessageSignature(%s, %x, %x)", public, msg, signature) +} diff --git a/apps/solana/account_test.go b/apps/solana/account_test.go new file mode 100644 index 00000000..b1e5140c --- /dev/null +++ b/apps/solana/account_test.go @@ -0,0 +1,29 @@ +package solana + +import ( + "testing" + + "github.com/gagliardetto/solana-go" + "github.com/stretchr/testify/assert" +) + +func TestVerifyPublic(t *testing.T) { + public := "fb17b60698d36d45bc624c8e210b4c845233c99a7ae312a27e883a8aa8444b9b" + err := VerifyHolderKey(public) + assert.Nil(t, err) + + key1, err := solana.NewRandomPrivateKey() + assert.Nil(t, err) + pub1 := key1.PublicKey() + t.Log(key1.String(), pub1.String()) + + key2, err := solana.NewRandomPrivateKey() + assert.Nil(t, err) + pub2 := key2.PublicKey() + t.Log(key2.String(), pub2.String()) + + pri3, err := solana.NewRandomPrivateKey() + assert.Nil(t, err) + pub3 := pri3.PublicKey() + t.Log(pri3.String(), pub3.String()) +} diff --git a/apps/solana/address.go b/apps/solana/address.go new file mode 100644 index 00000000..369f7836 --- /dev/null +++ b/apps/solana/address.go @@ -0,0 +1,85 @@ +package solana + +import ( + "encoding/binary" + + "github.com/MixinNetwork/safe/apps/solana/squads_mpl" + solana "github.com/gagliardetto/solana-go" +) + +const ( + DefaultAuthorityIndex uint32 = 1 +) + +func GetAuthorityPDA(ms solana.PublicKey, index uint32) solana.PublicKey { + indexBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(indexBytes, index) + + address, _, err := solana.FindProgramAddress( + [][]byte{ + []byte("squad"), + ms.Bytes(), + indexBytes, + []byte("authority"), + }, + squads_mpl.ProgramID, + ) + if err != nil { + panic(err) + } + return address +} + +func GetDefaultAuthorityPDA(ms solana.PublicKey) solana.PublicKey { + return GetAuthorityPDA(ms, DefaultAuthorityIndex) +} + +func GetMultisigPDA(createKey solana.PublicKey) solana.PublicKey { + address, _, err := solana.FindProgramAddress( + [][]byte{ + []byte("squad"), + createKey.Bytes(), + []byte("multisig"), + }, + squads_mpl.ProgramID, + ) + if err != nil { + panic(err) + } + return address +} + +func GetTransactionPDA(ms solana.PublicKey, nonce uint32) solana.PublicKey { + nonceBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(nonceBytes, nonce) + + address, _, err := solana.FindProgramAddress( + [][]byte{ + []byte("squad"), + ms.Bytes(), + nonceBytes, + []byte("transaction"), + }, + squads_mpl.ProgramID, + ) + if err != nil { + panic(err) + } + return address +} + +func GetInstructionPDA(txPda solana.PublicKey, index uint8) solana.PublicKey { + address, _, err := solana.FindProgramAddress( + [][]byte{ + []byte("squad"), + txPda.Bytes(), + {index}, + []byte("instruction"), + }, + squads_mpl.ProgramID, + ) + if err != nil { + panic(err) + } + return address +} diff --git a/apps/solana/common.go b/apps/solana/common.go new file mode 100644 index 00000000..a0f198af --- /dev/null +++ b/apps/solana/common.go @@ -0,0 +1,259 @@ +package solana + +import ( + "context" + "crypto/md5" + "encoding/hex" + "fmt" + "io" + "math/big" + + "github.com/MixinNetwork/mixin/logger" + "github.com/MixinNetwork/safe/apps/solana/squads_mpl" + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/programs/system" + "github.com/gagliardetto/solana-go/programs/token" + "github.com/gagliardetto/solana-go/rpc" + "github.com/gofrs/uuid" +) + +const ( + ChainSolana = 7 + + AssetChainSystem byte = 0 + AssetChainToken byte = 1 + AssetChainToken2022 byte = 2 + + SolanaMixinChainId = "64692c23-8971-4cf4-84a7-4dd1271dd887" + SolanaEmptyAddress = "11111111111111111111111111111111" + NativeTokenDecimals uint32 = 9 +) + +func init() { + programID := solana.MustPublicKeyFromBase58("SMPLecH534NA9acpos4G6x7uf3LWbCAwZQE9e8ZekMu") + squads_mpl.SetProgramID(programID) +} + +type Asset struct { + Address string + Id string + Symbol string + Name string + Decimals uint32 +} + +type Transfer struct { + // Signature is the signature of the transaction that contains the transfer. + Signature string + + // Index is the index of the transfer in the transaction. + Index int64 + + // TokenAddress is the address of the token that is being transferred. + // If the token is SPL Token, it will be the address of the mint. + // If the token is native SOL, it will be 'SolanaMintAddress'. + TokenAddress string + + // AssetId is the mixin version asset id + AssetId string + + Sender string + Receiver string + Value *big.Int +} + +func CheckFinalization(num uint64) bool { + return num >= 32 +} + +// GenerateAssetId generates a mixin version asset id from a Solana token address. +func GenerateAssetId(assetKey string) string { + _ = solana.MustPublicKeyFromBase58(assetKey) + return buildChainAssetId(SolanaMixinChainId, assetKey) +} + +func buildChainAssetId(base, asset string) string { + h := md5.New() + _, _ = io.WriteString(h, base) + _, _ = io.WriteString(h, asset) + sum := h.Sum(nil) + sum[6] = (sum[6] & 0x0f) | 0x30 + sum[8] = (sum[8] & 0x3f) | 0x80 + id, err := uuid.FromBytes(sum) + if err != nil { + panic(hex.EncodeToString(sum)) + } + return id.String() +} + +func (c *Client) VerifyDeposit(ctx context.Context, hash, assetAddress, destination string, index int64, amount *big.Int) (*Transfer, *rpc.GetTransactionResult, error) { + etx, err := c.RPCGetTransaction(ctx, hash) + logger.Printf("solana.RPCGetTransaction(%s) => %v %v", hash, etx, err) + if err != nil { + return nil, nil, fmt.Errorf("malicious solana deposit or node not in sync? %s %v", hash, err) + } + + tx, err := etx.Transaction.GetTransaction() + if err != nil { + panic(err) + } + + transfers, err := c.ExtractTransfersFromTransaction(ctx, tx, etx.Meta) + logger.Printf("solana.ExtractTransfersFromTransaction(%s) => %v %v", hash, transfers, err) + if err != nil { + return nil, nil, err + } + + for _, transfer := range transfers { + if transfer.TokenAddress == assetAddress && transfer.Index == index && transfer.Receiver == destination && amount.Cmp(transfer.Value) == 0 { + return transfer, etx, nil + } + } + + return nil, nil, nil +} + +func (c *Client) ExtractTransfersFromTransaction(ctx context.Context, tx *solana.Transaction, meta *rpc.TransactionMeta) ([]*Transfer, error) { + if meta == nil { + return nil, fmt.Errorf("meta is nil") + } + + if meta.Err != nil { + // Transaction failed, ignore + return nil, nil + } + + hash := tx.Signatures[0].String() + if err := c.processTransactionWithAddressLookups(ctx, tx); err != nil { + return nil, err + } + + msg := tx.Message + + var ( + transfers = []*Transfer{} + innerInstructions = map[uint16][]solana.CompiledInstruction{} + tokenAccounts = map[solana.PublicKey]token.Account{} + ) + + for _, inner := range meta.InnerInstructions { + innerInstructions[inner.Index] = inner.Instructions + } + + for _, balance := range meta.PreTokenBalances { + if account, err := msg.Account(balance.AccountIndex); err == nil { + tokenAccounts[account] = token.Account{ + Owner: *balance.Owner, + Mint: balance.Mint, + } + } + } + + for index, ix := range msg.Instructions { + baseIndex := int64(index+1) * 10000 + if transfer := extractTransfersFromInstruction(&msg, ix, tokenAccounts); transfer != nil { + transfer.Signature = hash + transfer.Index = baseIndex + transfers = append(transfers, transfer) + } + + for innerIndex, inner := range innerInstructions[uint16(index)] { + if transfer := extractTransfersFromInstruction(&msg, inner, tokenAccounts); transfer != nil { + transfer.Signature = hash + transfer.Index = baseIndex + int64(innerIndex) + 1 + transfers = append(transfers, transfer) + } + } + } + + return transfers, nil +} + +func extractTransfersFromInstruction(msg *solana.Message, cix solana.CompiledInstruction, tokenAccounts map[solana.PublicKey]token.Account) *Transfer { + programKey, err := msg.Program(cix.ProgramIDIndex) + if err != nil { + panic(err) + } + + accounts, err := cix.ResolveInstructionAccounts(msg) + if err != nil { + panic(err) + } + + switch programKey { + case system.ProgramID: + if transfer, ok := decodeSystemTransfer(accounts, cix.Data); ok { + return &Transfer{ + TokenAddress: SolanaEmptyAddress, + AssetId: SolanaMixinChainId, + Sender: transfer.GetFundingAccount().PublicKey.String(), + Receiver: transfer.GetRecipientAccount().PublicKey.String(), + Value: new(big.Int).SetUint64(*transfer.Lamports), + } + } + case solana.TokenProgramID, solana.Token2022ProgramID: + if transfer, ok := decodeTokenTransfer(accounts, cix.Data); ok { + from, ok := tokenAccounts[transfer.GetSourceAccount().PublicKey] + if !ok { + panic(fmt.Sprintf("token account not found: %s", transfer.GetSourceAccount().PublicKey.String())) + } + + to, ok := tokenAccounts[transfer.GetDestinationAccount().PublicKey] + if !ok { + panic(fmt.Sprintf("token account not found: %s", transfer.GetDestinationAccount().PublicKey.String())) + } + + return &Transfer{ + TokenAddress: from.Mint.String(), + AssetId: buildChainAssetId(SolanaMixinChainId, from.Mint.String()), + Sender: from.Owner.String(), + Receiver: to.Owner.String(), + Value: new(big.Int).SetUint64(*transfer.Amount), + } + } + } + + return nil +} + +func decodeSystemTransfer(accounts solana.AccountMetaSlice, data []byte) (*system.Transfer, bool) { + ix, err := system.DecodeInstruction(accounts, data) + if err != nil { + return nil, false + } + + if transfer, ok := ix.Impl.(*system.Transfer); ok { + return transfer, true + } + + if transferWithSeed, ok := ix.Impl.(*system.TransferWithSeed); ok { + t := system.NewTransferInstructionBuilder() + t.SetFundingAccount(transferWithSeed.GetFundingAccount().PublicKey) + t.SetRecipientAccount(transferWithSeed.GetRecipientAccount().PublicKey) + t.SetLamports(*transferWithSeed.Lamports) + return t, true + } + + return nil, false +} + +func decodeTokenTransfer(accounts solana.AccountMetaSlice, data []byte) (*token.Transfer, bool) { + ix, err := token.DecodeInstruction(accounts, data) + if err != nil { + return nil, false + } + + if transfer, ok := ix.Impl.(*token.Transfer); ok { + return transfer, true + } + + if transferChecked, ok := ix.Impl.(*token.TransferChecked); ok { + t := token.NewTransferInstructionBuilder() + t.SetSourceAccount(transferChecked.GetSourceAccount().PublicKey) + t.SetDestinationAccount(transferChecked.GetDestinationAccount().PublicKey) + t.SetAmount(*transferChecked.Amount) + return t, true + } + + return nil, false +} diff --git a/apps/solana/rpc.go b/apps/solana/rpc.go new file mode 100644 index 00000000..8faec970 --- /dev/null +++ b/apps/solana/rpc.go @@ -0,0 +1,227 @@ +package solana + +import ( + "context" + "errors" + "fmt" + + solana "github.com/gagliardetto/solana-go" + lookup "github.com/gagliardetto/solana-go/programs/address-lookup-table" + "github.com/gagliardetto/solana-go/programs/token" + "github.com/gagliardetto/solana-go/rpc" + "github.com/gagliardetto/solana-go/rpc/ws" +) + +func NewClient(rpcEndpoint, wsEndpoint string) *Client { + return &Client{ + rpcEndpoint: rpcEndpoint, + rpcClient: rpc.New(rpcEndpoint), + wsEndpoint: wsEndpoint, + } +} + +type Client struct { + rpcEndpoint string + wsEndpoint string + + rpcClient *rpc.Client +} + +func (c *Client) getRPCClient() *rpc.Client { + return c.rpcClient +} + +func (c *Client) connectWs(ctx context.Context) (*ws.Client, error) { + return ws.Connect(ctx, c.wsEndpoint) +} + +func (c *Client) RPCGetBlock(ctx context.Context, slot uint64) (*rpc.GetBlockResult, error) { + client := c.getRPCClient() + block, err := client.GetBlockWithOpts(ctx, slot, &rpc.GetBlockOpts{ + Encoding: solana.EncodingBase64, + Commitment: rpc.CommitmentFinalized, + MaxSupportedTransactionVersion: &rpc.MaxSupportedTransactionVersion1, + }) + if err != nil { + return nil, err + } + return block, nil +} + +func (c *Client) RPCGetTransaction(ctx context.Context, signature string) (*rpc.GetTransactionResult, error) { + client := c.getRPCClient() + r, err := client.GetTransaction( + ctx, + solana.MustSignatureFromBase58(signature), + &rpc.GetTransactionOpts{ + Encoding: solana.EncodingBase58, + MaxSupportedTransactionVersion: &rpc.MaxSupportedTransactionVersion1, + Commitment: rpc.CommitmentFinalized, + }) + if err != nil { + return nil, err + } + + if r.Meta == nil { + return nil, fmt.Errorf("meta is nil") + } + + return r, nil +} + +func (c *Client) RPCGetBlockHeight(ctx context.Context) (int64, solana.Hash, error) { + client := c.getRPCClient() + result, err := client.GetLatestBlockhash(ctx, rpc.CommitmentFinalized) + if err != nil { + return 0, solana.Hash{}, err + } + + return int64(result.Value.LastValidBlockHeight), result.Value.Blockhash, nil +} + +func (c *Client) RPCGetUnitPrice(ctx context.Context) (uint64, error) { + client := c.getRPCClient() + // 获取最近的费用数据 + fees, err := client.GetRecentPrioritizationFees(ctx, nil) + if err != nil { + return 0, fmt.Errorf("get recent prioritization fees: %w", err) + } + + if len(fees) == 0 { + // 如果没有最近的费用数据,返回默认的最小费用 (5000 lamports) + return 5000, nil + } + + // 找出最低的费用 + minFee := uint64(^uint(0)) // 设置为最大uint64值 + for _, fee := range fees { + if fee.PrioritizationFee < minFee { + minFee = fee.PrioritizationFee + } + } + + return minFee, nil +} + +func (c *Client) RPCGetBlockByHeight(ctx context.Context, height uint64) (*rpc.GetBlockResult, error) { + client := c.getRPCClient() + block, err := client.GetBlockWithOpts(ctx, height, &rpc.GetBlockOpts{ + Encoding: solana.EncodingBase64, + Commitment: rpc.CommitmentFinalized, + MaxSupportedTransactionVersion: &rpc.MaxSupportedTransactionVersion1, + TransactionDetails: rpc.TransactionDetailsFull, + }) + if err != nil { + if errors.Is(err, rpc.ErrNotFound) { + return nil, nil + } + return nil, err + } + return block, nil +} + +func (c *Client) RPCGetAsset(ctx context.Context, address string) (*Asset, error) { + client := c.getRPCClient() + var mint token.Mint + if err := client.GetAccountDataInto(ctx, solana.MPK(address), &mint); err != nil { + return nil, err + } + + metadata, err := c.getAssetMetadata(ctx, address) + if err != nil { + return nil, err + } + + asset := &Asset{ + Address: address, + Id: GenerateAssetId(address), + Decimals: uint32(mint.Decimals), + Symbol: metadata.Symbol, + Name: metadata.Name, + } + + return asset, nil +} + +type AssetMetadata struct { + Symbol string `json:"symbol"` + Name string `json:"name"` + Description string `json:"description"` +} + +func (c *Client) getAssetMetadata(ctx context.Context, address string) (*AssetMetadata, error) { + client := c.getRPCClient() + + var resp struct { + Content struct { + Metadata AssetMetadata `json:"metadata"` + } `json:"content"` + } + + opt := map[string]any{ + "id": address, + } + + if err := client.RPCCallForInto(ctx, &resp, "getAsset", []any{opt}); err != nil { + return nil, err + } + + return &resp.Content.Metadata, nil +} + +// processTransactionWithAddressLookups resolves the address lookups in the transaction. +func (c *Client) processTransactionWithAddressLookups(ctx context.Context, txx *solana.Transaction) error { + if txx.Message.IsResolved() { + return nil + } + + if !txx.Message.IsVersioned() { + // tx is not versioned, ignore + return nil + } + + tblKeys := txx.Message.GetAddressTableLookups().GetTableIDs() + if len(tblKeys) == 0 { + return nil + } + numLookups := txx.Message.GetAddressTableLookups().NumLookups() + if numLookups == 0 { + return nil + } + + rpcClient := c.getRPCClient() + + resolutions := make(map[solana.PublicKey]solana.PublicKeySlice) + for _, key := range tblKeys { + info, err := rpcClient.GetAccountInfo(ctx, key) + if err != nil { + return fmt.Errorf("get account info: %w", err) + } + + tableContent, err := lookup.DecodeAddressLookupTableState(info.GetBinary()) + if err != nil { + return fmt.Errorf("decode address lookup table state: %w", err) + } + + resolutions[key] = tableContent.Addresses + } + + if err := txx.Message.SetAddressTables(resolutions); err != nil { + return fmt.Errorf("set address tables: %w", err) + } + + if err := txx.Message.ResolveLookups(); err != nil { + return fmt.Errorf("resolve lookups: %w", err) + } + + return nil +} + +func (c *Client) SendTransaction(ctx context.Context, tx *solana.Transaction) (string, error) { + client := c.getRPCClient() + sig, err := client.SendTransaction(ctx, tx) + if err != nil { + return "", err + } + return sig.String(), nil +} diff --git a/apps/solana/rpc_test.go b/apps/solana/rpc_test.go new file mode 100644 index 00000000..667322a4 --- /dev/null +++ b/apps/solana/rpc_test.go @@ -0,0 +1,48 @@ +package solana + +import ( + "context" + "testing" + + solana "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/rpc" +) + +func TestRPCGetTransaction(t *testing.T) { + c := NewClient(rpc.MainNetBeta.RPC, rpc.MainNetBeta.WS) + r, err := c.RPCGetTransaction(context.TODO(), "2nijNeL8mYp97HgJbv6bi5bfa6CJNttpZKCXM7SRnQ5ZvKcT3GjRqnVkzf8ardVx3h4yKZ8UD7c5GJn2fh8r5Ajm") + if err != nil { + t.Fatal(err) + } + + tx, err := r.Transaction.GetTransaction() + if err != nil { + t.Fatal(err) + } + + if err := c.processTransactionWithAddressLookups(context.TODO(), tx); err != nil { + t.Fatal(err) + } + + t.Log("instructions", len(tx.Message.Instructions), len(tx.Message.AccountKeys)) + + if r.Meta != nil { + t.Log("loaded addresses", len(r.Meta.LoadedAddresses.ReadOnly)+len(r.Meta.LoadedAddresses.Writable)) + t.Log("balance changes", len(r.Meta.PreBalances), len(r.Meta.PostBalances)) + + t.Log("inner instructions", len(r.Meta.InnerInstructions)) + + for _, i := range r.Meta.InnerInstructions { + t.Log("inner instruction", i.Index, len(i.Instructions)) + } + + for _, token := range r.Meta.PreTokenBalances { + t.Log("pre token balance", token.Mint, token.Owner.String(), token.UiTokenAmount.Decimals) + } + } +} + +func TestEmptyAddress(t *testing.T) { + var pk solana.PublicKey + t.Log(pk.String()) +} diff --git a/apps/solana/squads_mpl/ActivateTransaction.go b/apps/solana/squads_mpl/ActivateTransaction.go new file mode 100644 index 00000000..610ba48e --- /dev/null +++ b/apps/solana/squads_mpl/ActivateTransaction.go @@ -0,0 +1,137 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "errors" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" +) + +// Instruction to set the state of a transaction "active". +// "active" transactions can then be signed off by multisig members +type ActivateTransaction struct { + + // [0] = [] multisig + // + // [1] = [WRITE] transaction + // + // [2] = [WRITE, SIGNER] creator + ag_solanago.AccountMetaSlice `bin:"-"` +} + +// NewActivateTransactionInstructionBuilder creates a new `ActivateTransaction` instruction builder. +func NewActivateTransactionInstructionBuilder() *ActivateTransaction { + nd := &ActivateTransaction{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3), + } + return nd +} + +// SetMultisigAccount sets the "multisig" account. +func (inst *ActivateTransaction) SetMultisigAccount(multisig ag_solanago.PublicKey) *ActivateTransaction { + inst.AccountMetaSlice[0] = ag_solanago.Meta(multisig) + return inst +} + +// GetMultisigAccount gets the "multisig" account. +func (inst *ActivateTransaction) GetMultisigAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(0) +} + +// SetTransactionAccount sets the "transaction" account. +func (inst *ActivateTransaction) SetTransactionAccount(transaction ag_solanago.PublicKey) *ActivateTransaction { + inst.AccountMetaSlice[1] = ag_solanago.Meta(transaction).WRITE() + return inst +} + +// GetTransactionAccount gets the "transaction" account. +func (inst *ActivateTransaction) GetTransactionAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(1) +} + +// SetCreatorAccount sets the "creator" account. +func (inst *ActivateTransaction) SetCreatorAccount(creator ag_solanago.PublicKey) *ActivateTransaction { + inst.AccountMetaSlice[2] = ag_solanago.Meta(creator).WRITE().SIGNER() + return inst +} + +// GetCreatorAccount gets the "creator" account. +func (inst *ActivateTransaction) GetCreatorAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(2) +} + +func (inst ActivateTransaction) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: Instruction_ActivateTransaction, + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst ActivateTransaction) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *ActivateTransaction) Validate() error { + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Multisig is not set") + } + if inst.AccountMetaSlice[1] == nil { + return errors.New("accounts.Transaction is not set") + } + if inst.AccountMetaSlice[2] == nil { + return errors.New("accounts.Creator is not set") + } + } + return nil +} + +func (inst *ActivateTransaction) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("ActivateTransaction")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) + + // Accounts of the instruction: + instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" multisig", inst.AccountMetaSlice.Get(0))) + accountsBranch.Child(ag_format.Meta("transaction", inst.AccountMetaSlice.Get(1))) + accountsBranch.Child(ag_format.Meta(" creator", inst.AccountMetaSlice.Get(2))) + }) + }) + }) +} + +func (obj ActivateTransaction) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + return nil +} +func (obj *ActivateTransaction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + return nil +} + +// NewActivateTransactionInstruction declares a new ActivateTransaction instruction with the provided parameters and accounts. +func NewActivateTransactionInstruction( + // Accounts: + multisig ag_solanago.PublicKey, + transaction ag_solanago.PublicKey, + creator ag_solanago.PublicKey) *ActivateTransaction { + return NewActivateTransactionInstructionBuilder(). + SetMultisigAccount(multisig). + SetTransactionAccount(transaction). + SetCreatorAccount(creator) +} diff --git a/apps/solana/squads_mpl/ActivateTransaction_test.go b/apps/solana/squads_mpl/ActivateTransaction_test.go new file mode 100644 index 00000000..a4e11028 --- /dev/null +++ b/apps/solana/squads_mpl/ActivateTransaction_test.go @@ -0,0 +1,32 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_ActivateTransaction(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("ActivateTransaction"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(ActivateTransaction) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + got := new(ActivateTransaction) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/apps/solana/squads_mpl/AddAuthority.go b/apps/solana/squads_mpl/AddAuthority.go new file mode 100644 index 00000000..2caa263d --- /dev/null +++ b/apps/solana/squads_mpl/AddAuthority.go @@ -0,0 +1,102 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "errors" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" +) + +// instruction to increase the authority value tracked in the multisig +// This is optional, as authorities are simply PDAs, however it may be helpful +// to keep track of commonly used authorities in a UI. +// This has no functional impact on the multisig or its functionality, but +// can be used to track commonly used authorities (ie, vault 1, vault 2, etc.) +type AddAuthority struct { + + // [0] = [WRITE, SIGNER] multisig + ag_solanago.AccountMetaSlice `bin:"-"` +} + +// NewAddAuthorityInstructionBuilder creates a new `AddAuthority` instruction builder. +func NewAddAuthorityInstructionBuilder() *AddAuthority { + nd := &AddAuthority{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 1), + } + return nd +} + +// SetMultisigAccount sets the "multisig" account. +func (inst *AddAuthority) SetMultisigAccount(multisig ag_solanago.PublicKey) *AddAuthority { + inst.AccountMetaSlice[0] = ag_solanago.Meta(multisig).WRITE().SIGNER() + return inst +} + +// GetMultisigAccount gets the "multisig" account. +func (inst *AddAuthority) GetMultisigAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(0) +} + +func (inst AddAuthority) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: Instruction_AddAuthority, + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst AddAuthority) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *AddAuthority) Validate() error { + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Multisig is not set") + } + } + return nil +} + +func (inst *AddAuthority) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("AddAuthority")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) + + // Accounts of the instruction: + instructionBranch.Child("Accounts[len=1]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("multisig", inst.AccountMetaSlice.Get(0))) + }) + }) + }) +} + +func (obj AddAuthority) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + return nil +} +func (obj *AddAuthority) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + return nil +} + +// NewAddAuthorityInstruction declares a new AddAuthority instruction with the provided parameters and accounts. +func NewAddAuthorityInstruction( + // Accounts: + multisig ag_solanago.PublicKey) *AddAuthority { + return NewAddAuthorityInstructionBuilder(). + SetMultisigAccount(multisig) +} diff --git a/apps/solana/squads_mpl/AddAuthority_test.go b/apps/solana/squads_mpl/AddAuthority_test.go new file mode 100644 index 00000000..a6a928e4 --- /dev/null +++ b/apps/solana/squads_mpl/AddAuthority_test.go @@ -0,0 +1,32 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_AddAuthority(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("AddAuthority"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(AddAuthority) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + got := new(AddAuthority) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/apps/solana/squads_mpl/AddInstruction.go b/apps/solana/squads_mpl/AddInstruction.go new file mode 100644 index 00000000..7c5e44b9 --- /dev/null +++ b/apps/solana/squads_mpl/AddInstruction.go @@ -0,0 +1,206 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "errors" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" +) + +// Instruction to attach an instruction to a transaction. +// Transactions must be in the "draft" status, and any +// signer (aside from execution payer) specified in an instruction +// must match the authority PDA specified during the transaction creation. +type AddInstruction struct { + IncomingInstruction *IncomingInstruction + + // [0] = [] multisig + // + // [1] = [WRITE] transaction + // + // [2] = [WRITE] instruction + // + // [3] = [WRITE, SIGNER] creator + // + // [4] = [] systemProgram + ag_solanago.AccountMetaSlice `bin:"-"` +} + +// NewAddInstructionInstructionBuilder creates a new `AddInstruction` instruction builder. +func NewAddInstructionInstructionBuilder() *AddInstruction { + nd := &AddInstruction{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 5), + } + return nd +} + +// SetIncomingInstruction sets the "incomingInstruction" parameter. +func (inst *AddInstruction) SetIncomingInstruction(incomingInstruction IncomingInstruction) *AddInstruction { + inst.IncomingInstruction = &incomingInstruction + return inst +} + +// SetMultisigAccount sets the "multisig" account. +func (inst *AddInstruction) SetMultisigAccount(multisig ag_solanago.PublicKey) *AddInstruction { + inst.AccountMetaSlice[0] = ag_solanago.Meta(multisig) + return inst +} + +// GetMultisigAccount gets the "multisig" account. +func (inst *AddInstruction) GetMultisigAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(0) +} + +// SetTransactionAccount sets the "transaction" account. +func (inst *AddInstruction) SetTransactionAccount(transaction ag_solanago.PublicKey) *AddInstruction { + inst.AccountMetaSlice[1] = ag_solanago.Meta(transaction).WRITE() + return inst +} + +// GetTransactionAccount gets the "transaction" account. +func (inst *AddInstruction) GetTransactionAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(1) +} + +// SetInstructionAccount sets the "instruction" account. +func (inst *AddInstruction) SetInstructionAccount(instruction ag_solanago.PublicKey) *AddInstruction { + inst.AccountMetaSlice[2] = ag_solanago.Meta(instruction).WRITE() + return inst +} + +// GetInstructionAccount gets the "instruction" account. +func (inst *AddInstruction) GetInstructionAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(2) +} + +// SetCreatorAccount sets the "creator" account. +func (inst *AddInstruction) SetCreatorAccount(creator ag_solanago.PublicKey) *AddInstruction { + inst.AccountMetaSlice[3] = ag_solanago.Meta(creator).WRITE().SIGNER() + return inst +} + +// GetCreatorAccount gets the "creator" account. +func (inst *AddInstruction) GetCreatorAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(3) +} + +// SetSystemProgramAccount sets the "systemProgram" account. +func (inst *AddInstruction) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *AddInstruction { + inst.AccountMetaSlice[4] = ag_solanago.Meta(systemProgram) + return inst +} + +// GetSystemProgramAccount gets the "systemProgram" account. +func (inst *AddInstruction) GetSystemProgramAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(4) +} + +func (inst AddInstruction) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: Instruction_AddInstruction, + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst AddInstruction) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *AddInstruction) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.IncomingInstruction == nil { + return errors.New("IncomingInstruction parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Multisig is not set") + } + if inst.AccountMetaSlice[1] == nil { + return errors.New("accounts.Transaction is not set") + } + if inst.AccountMetaSlice[2] == nil { + return errors.New("accounts.Instruction is not set") + } + if inst.AccountMetaSlice[3] == nil { + return errors.New("accounts.Creator is not set") + } + if inst.AccountMetaSlice[4] == nil { + return errors.New("accounts.SystemProgram is not set") + } + } + return nil +} + +func (inst *AddInstruction) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("AddInstruction")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("IncomingInstruction", *inst.IncomingInstruction)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts[len=5]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" multisig", inst.AccountMetaSlice.Get(0))) + accountsBranch.Child(ag_format.Meta(" transaction", inst.AccountMetaSlice.Get(1))) + accountsBranch.Child(ag_format.Meta(" instruction", inst.AccountMetaSlice.Get(2))) + accountsBranch.Child(ag_format.Meta(" creator", inst.AccountMetaSlice.Get(3))) + accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice.Get(4))) + }) + }) + }) +} + +func (obj AddInstruction) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `IncomingInstruction` param: + err = encoder.Encode(obj.IncomingInstruction) + if err != nil { + return err + } + return nil +} +func (obj *AddInstruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `IncomingInstruction`: + err = decoder.Decode(&obj.IncomingInstruction) + if err != nil { + return err + } + return nil +} + +// NewAddInstructionInstruction declares a new AddInstruction instruction with the provided parameters and accounts. +func NewAddInstructionInstruction( + // Parameters: + incomingInstruction IncomingInstruction, + // Accounts: + multisig ag_solanago.PublicKey, + transaction ag_solanago.PublicKey, + instruction ag_solanago.PublicKey, + creator ag_solanago.PublicKey, + systemProgram ag_solanago.PublicKey) *AddInstruction { + return NewAddInstructionInstructionBuilder(). + SetIncomingInstruction(incomingInstruction). + SetMultisigAccount(multisig). + SetTransactionAccount(transaction). + SetInstructionAccount(instruction). + SetCreatorAccount(creator). + SetSystemProgramAccount(systemProgram) +} diff --git a/apps/solana/squads_mpl/AddInstruction_test.go b/apps/solana/squads_mpl/AddInstruction_test.go new file mode 100644 index 00000000..8bbd7481 --- /dev/null +++ b/apps/solana/squads_mpl/AddInstruction_test.go @@ -0,0 +1,32 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_AddInstruction(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("AddInstruction"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(AddInstruction) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + got := new(AddInstruction) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/apps/solana/squads_mpl/AddMember.go b/apps/solana/squads_mpl/AddMember.go new file mode 100644 index 00000000..3278daf1 --- /dev/null +++ b/apps/solana/squads_mpl/AddMember.go @@ -0,0 +1,168 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "errors" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" +) + +// The instruction to add a new member to the multisig. +// Adds member/key to the multisig and reallocates space if neccessary +// If the multisig needs to be reallocated, it must be prefunded with +// enough lamports to cover the new size. +type AddMember struct { + NewMember *ag_solanago.PublicKey + + // [0] = [WRITE, SIGNER] multisig + // + // [1] = [] rent + // + // [2] = [] systemProgram + ag_solanago.AccountMetaSlice `bin:"-"` +} + +// NewAddMemberInstructionBuilder creates a new `AddMember` instruction builder. +func NewAddMemberInstructionBuilder() *AddMember { + nd := &AddMember{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3), + } + return nd +} + +// SetNewMember sets the "newMember" parameter. +func (inst *AddMember) SetNewMember(newMember ag_solanago.PublicKey) *AddMember { + inst.NewMember = &newMember + return inst +} + +// SetMultisigAccount sets the "multisig" account. +func (inst *AddMember) SetMultisigAccount(multisig ag_solanago.PublicKey) *AddMember { + inst.AccountMetaSlice[0] = ag_solanago.Meta(multisig).WRITE().SIGNER() + return inst +} + +// GetMultisigAccount gets the "multisig" account. +func (inst *AddMember) GetMultisigAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(0) +} + +// SetRentAccount sets the "rent" account. +func (inst *AddMember) SetRentAccount(rent ag_solanago.PublicKey) *AddMember { + inst.AccountMetaSlice[1] = ag_solanago.Meta(rent) + return inst +} + +// GetRentAccount gets the "rent" account. +func (inst *AddMember) GetRentAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(1) +} + +// SetSystemProgramAccount sets the "systemProgram" account. +func (inst *AddMember) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *AddMember { + inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram) + return inst +} + +// GetSystemProgramAccount gets the "systemProgram" account. +func (inst *AddMember) GetSystemProgramAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(2) +} + +func (inst AddMember) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: Instruction_AddMember, + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst AddMember) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *AddMember) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.NewMember == nil { + return errors.New("NewMember parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Multisig is not set") + } + if inst.AccountMetaSlice[1] == nil { + return errors.New("accounts.Rent is not set") + } + if inst.AccountMetaSlice[2] == nil { + return errors.New("accounts.SystemProgram is not set") + } + } + return nil +} + +func (inst *AddMember) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("AddMember")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("NewMember", *inst.NewMember)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" multisig", inst.AccountMetaSlice.Get(0))) + accountsBranch.Child(ag_format.Meta(" rent", inst.AccountMetaSlice.Get(1))) + accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice.Get(2))) + }) + }) + }) +} + +func (obj AddMember) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `NewMember` param: + err = encoder.Encode(obj.NewMember) + if err != nil { + return err + } + return nil +} +func (obj *AddMember) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `NewMember`: + err = decoder.Decode(&obj.NewMember) + if err != nil { + return err + } + return nil +} + +// NewAddMemberInstruction declares a new AddMember instruction with the provided parameters and accounts. +func NewAddMemberInstruction( + // Parameters: + newMember ag_solanago.PublicKey, + // Accounts: + multisig ag_solanago.PublicKey, + rent ag_solanago.PublicKey, + systemProgram ag_solanago.PublicKey) *AddMember { + return NewAddMemberInstructionBuilder(). + SetNewMember(newMember). + SetMultisigAccount(multisig). + SetRentAccount(rent). + SetSystemProgramAccount(systemProgram) +} diff --git a/apps/solana/squads_mpl/AddMemberAndChangeThreshold.go b/apps/solana/squads_mpl/AddMemberAndChangeThreshold.go new file mode 100644 index 00000000..618a49ba --- /dev/null +++ b/apps/solana/squads_mpl/AddMemberAndChangeThreshold.go @@ -0,0 +1,188 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "errors" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" +) + +// The instruction to change the threshold of the multisig and simultaneously add a member +type AddMemberAndChangeThreshold struct { + NewMember *ag_solanago.PublicKey + NewThreshold *uint16 + + // [0] = [WRITE, SIGNER] multisig + // + // [1] = [] rent + // + // [2] = [] systemProgram + ag_solanago.AccountMetaSlice `bin:"-"` +} + +// NewAddMemberAndChangeThresholdInstructionBuilder creates a new `AddMemberAndChangeThreshold` instruction builder. +func NewAddMemberAndChangeThresholdInstructionBuilder() *AddMemberAndChangeThreshold { + nd := &AddMemberAndChangeThreshold{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3), + } + return nd +} + +// SetNewMember sets the "newMember" parameter. +func (inst *AddMemberAndChangeThreshold) SetNewMember(newMember ag_solanago.PublicKey) *AddMemberAndChangeThreshold { + inst.NewMember = &newMember + return inst +} + +// SetNewThreshold sets the "newThreshold" parameter. +func (inst *AddMemberAndChangeThreshold) SetNewThreshold(newThreshold uint16) *AddMemberAndChangeThreshold { + inst.NewThreshold = &newThreshold + return inst +} + +// SetMultisigAccount sets the "multisig" account. +func (inst *AddMemberAndChangeThreshold) SetMultisigAccount(multisig ag_solanago.PublicKey) *AddMemberAndChangeThreshold { + inst.AccountMetaSlice[0] = ag_solanago.Meta(multisig).WRITE().SIGNER() + return inst +} + +// GetMultisigAccount gets the "multisig" account. +func (inst *AddMemberAndChangeThreshold) GetMultisigAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(0) +} + +// SetRentAccount sets the "rent" account. +func (inst *AddMemberAndChangeThreshold) SetRentAccount(rent ag_solanago.PublicKey) *AddMemberAndChangeThreshold { + inst.AccountMetaSlice[1] = ag_solanago.Meta(rent) + return inst +} + +// GetRentAccount gets the "rent" account. +func (inst *AddMemberAndChangeThreshold) GetRentAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(1) +} + +// SetSystemProgramAccount sets the "systemProgram" account. +func (inst *AddMemberAndChangeThreshold) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *AddMemberAndChangeThreshold { + inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram) + return inst +} + +// GetSystemProgramAccount gets the "systemProgram" account. +func (inst *AddMemberAndChangeThreshold) GetSystemProgramAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(2) +} + +func (inst AddMemberAndChangeThreshold) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: Instruction_AddMemberAndChangeThreshold, + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst AddMemberAndChangeThreshold) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *AddMemberAndChangeThreshold) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.NewMember == nil { + return errors.New("NewMember parameter is not set") + } + if inst.NewThreshold == nil { + return errors.New("NewThreshold parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Multisig is not set") + } + if inst.AccountMetaSlice[1] == nil { + return errors.New("accounts.Rent is not set") + } + if inst.AccountMetaSlice[2] == nil { + return errors.New("accounts.SystemProgram is not set") + } + } + return nil +} + +func (inst *AddMemberAndChangeThreshold) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("AddMemberAndChangeThreshold")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params[len=2]").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param(" NewMember", *inst.NewMember)) + paramsBranch.Child(ag_format.Param("NewThreshold", *inst.NewThreshold)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" multisig", inst.AccountMetaSlice.Get(0))) + accountsBranch.Child(ag_format.Meta(" rent", inst.AccountMetaSlice.Get(1))) + accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice.Get(2))) + }) + }) + }) +} + +func (obj AddMemberAndChangeThreshold) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `NewMember` param: + err = encoder.Encode(obj.NewMember) + if err != nil { + return err + } + // Serialize `NewThreshold` param: + err = encoder.Encode(obj.NewThreshold) + if err != nil { + return err + } + return nil +} +func (obj *AddMemberAndChangeThreshold) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `NewMember`: + err = decoder.Decode(&obj.NewMember) + if err != nil { + return err + } + // Deserialize `NewThreshold`: + err = decoder.Decode(&obj.NewThreshold) + if err != nil { + return err + } + return nil +} + +// NewAddMemberAndChangeThresholdInstruction declares a new AddMemberAndChangeThreshold instruction with the provided parameters and accounts. +func NewAddMemberAndChangeThresholdInstruction( + // Parameters: + newMember ag_solanago.PublicKey, + newThreshold uint16, + // Accounts: + multisig ag_solanago.PublicKey, + rent ag_solanago.PublicKey, + systemProgram ag_solanago.PublicKey) *AddMemberAndChangeThreshold { + return NewAddMemberAndChangeThresholdInstructionBuilder(). + SetNewMember(newMember). + SetNewThreshold(newThreshold). + SetMultisigAccount(multisig). + SetRentAccount(rent). + SetSystemProgramAccount(systemProgram) +} diff --git a/apps/solana/squads_mpl/AddMemberAndChangeThreshold_test.go b/apps/solana/squads_mpl/AddMemberAndChangeThreshold_test.go new file mode 100644 index 00000000..6d93a0a2 --- /dev/null +++ b/apps/solana/squads_mpl/AddMemberAndChangeThreshold_test.go @@ -0,0 +1,32 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_AddMemberAndChangeThreshold(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("AddMemberAndChangeThreshold"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(AddMemberAndChangeThreshold) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + got := new(AddMemberAndChangeThreshold) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/apps/solana/squads_mpl/AddMember_test.go b/apps/solana/squads_mpl/AddMember_test.go new file mode 100644 index 00000000..a6ecf576 --- /dev/null +++ b/apps/solana/squads_mpl/AddMember_test.go @@ -0,0 +1,32 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_AddMember(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("AddMember"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(AddMember) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + got := new(AddMember) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/apps/solana/squads_mpl/ApproveTransaction.go b/apps/solana/squads_mpl/ApproveTransaction.go new file mode 100644 index 00000000..210602a8 --- /dev/null +++ b/apps/solana/squads_mpl/ApproveTransaction.go @@ -0,0 +1,137 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "errors" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" +) + +// Instruction to approve a transaction on behalf of a member. +// The transaction must have an "active" status +type ApproveTransaction struct { + + // [0] = [] multisig + // + // [1] = [WRITE] transaction + // + // [2] = [WRITE, SIGNER] member + ag_solanago.AccountMetaSlice `bin:"-"` +} + +// NewApproveTransactionInstructionBuilder creates a new `ApproveTransaction` instruction builder. +func NewApproveTransactionInstructionBuilder() *ApproveTransaction { + nd := &ApproveTransaction{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3), + } + return nd +} + +// SetMultisigAccount sets the "multisig" account. +func (inst *ApproveTransaction) SetMultisigAccount(multisig ag_solanago.PublicKey) *ApproveTransaction { + inst.AccountMetaSlice[0] = ag_solanago.Meta(multisig) + return inst +} + +// GetMultisigAccount gets the "multisig" account. +func (inst *ApproveTransaction) GetMultisigAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(0) +} + +// SetTransactionAccount sets the "transaction" account. +func (inst *ApproveTransaction) SetTransactionAccount(transaction ag_solanago.PublicKey) *ApproveTransaction { + inst.AccountMetaSlice[1] = ag_solanago.Meta(transaction).WRITE() + return inst +} + +// GetTransactionAccount gets the "transaction" account. +func (inst *ApproveTransaction) GetTransactionAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(1) +} + +// SetMemberAccount sets the "member" account. +func (inst *ApproveTransaction) SetMemberAccount(member ag_solanago.PublicKey) *ApproveTransaction { + inst.AccountMetaSlice[2] = ag_solanago.Meta(member).WRITE().SIGNER() + return inst +} + +// GetMemberAccount gets the "member" account. +func (inst *ApproveTransaction) GetMemberAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(2) +} + +func (inst ApproveTransaction) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: Instruction_ApproveTransaction, + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst ApproveTransaction) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *ApproveTransaction) Validate() error { + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Multisig is not set") + } + if inst.AccountMetaSlice[1] == nil { + return errors.New("accounts.Transaction is not set") + } + if inst.AccountMetaSlice[2] == nil { + return errors.New("accounts.Member is not set") + } + } + return nil +} + +func (inst *ApproveTransaction) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("ApproveTransaction")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) + + // Accounts of the instruction: + instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" multisig", inst.AccountMetaSlice.Get(0))) + accountsBranch.Child(ag_format.Meta("transaction", inst.AccountMetaSlice.Get(1))) + accountsBranch.Child(ag_format.Meta(" member", inst.AccountMetaSlice.Get(2))) + }) + }) + }) +} + +func (obj ApproveTransaction) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + return nil +} +func (obj *ApproveTransaction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + return nil +} + +// NewApproveTransactionInstruction declares a new ApproveTransaction instruction with the provided parameters and accounts. +func NewApproveTransactionInstruction( + // Accounts: + multisig ag_solanago.PublicKey, + transaction ag_solanago.PublicKey, + member ag_solanago.PublicKey) *ApproveTransaction { + return NewApproveTransactionInstructionBuilder(). + SetMultisigAccount(multisig). + SetTransactionAccount(transaction). + SetMemberAccount(member) +} diff --git a/apps/solana/squads_mpl/ApproveTransaction_test.go b/apps/solana/squads_mpl/ApproveTransaction_test.go new file mode 100644 index 00000000..d34abffd --- /dev/null +++ b/apps/solana/squads_mpl/ApproveTransaction_test.go @@ -0,0 +1,32 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_ApproveTransaction(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("ApproveTransaction"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(ApproveTransaction) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + got := new(ApproveTransaction) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/apps/solana/squads_mpl/CancelTransaction.go b/apps/solana/squads_mpl/CancelTransaction.go new file mode 100644 index 00000000..ed0f1b3b --- /dev/null +++ b/apps/solana/squads_mpl/CancelTransaction.go @@ -0,0 +1,159 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "errors" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" +) + +// Instruction to cancel a transaction. +// Transactions must be in the "executeReady" status. +// Transaction will only be cancelled if the number of +// cancellations reaches the threshold. A cancelled +// transaction will no longer be able to be executed. +type CancelTransaction struct { + + // [0] = [WRITE] multisig + // + // [1] = [WRITE] transaction + // + // [2] = [WRITE, SIGNER] member + // + // [3] = [] systemProgram + ag_solanago.AccountMetaSlice `bin:"-"` +} + +// NewCancelTransactionInstructionBuilder creates a new `CancelTransaction` instruction builder. +func NewCancelTransactionInstructionBuilder() *CancelTransaction { + nd := &CancelTransaction{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4), + } + return nd +} + +// SetMultisigAccount sets the "multisig" account. +func (inst *CancelTransaction) SetMultisigAccount(multisig ag_solanago.PublicKey) *CancelTransaction { + inst.AccountMetaSlice[0] = ag_solanago.Meta(multisig).WRITE() + return inst +} + +// GetMultisigAccount gets the "multisig" account. +func (inst *CancelTransaction) GetMultisigAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(0) +} + +// SetTransactionAccount sets the "transaction" account. +func (inst *CancelTransaction) SetTransactionAccount(transaction ag_solanago.PublicKey) *CancelTransaction { + inst.AccountMetaSlice[1] = ag_solanago.Meta(transaction).WRITE() + return inst +} + +// GetTransactionAccount gets the "transaction" account. +func (inst *CancelTransaction) GetTransactionAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(1) +} + +// SetMemberAccount sets the "member" account. +func (inst *CancelTransaction) SetMemberAccount(member ag_solanago.PublicKey) *CancelTransaction { + inst.AccountMetaSlice[2] = ag_solanago.Meta(member).WRITE().SIGNER() + return inst +} + +// GetMemberAccount gets the "member" account. +func (inst *CancelTransaction) GetMemberAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(2) +} + +// SetSystemProgramAccount sets the "systemProgram" account. +func (inst *CancelTransaction) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *CancelTransaction { + inst.AccountMetaSlice[3] = ag_solanago.Meta(systemProgram) + return inst +} + +// GetSystemProgramAccount gets the "systemProgram" account. +func (inst *CancelTransaction) GetSystemProgramAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(3) +} + +func (inst CancelTransaction) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: Instruction_CancelTransaction, + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst CancelTransaction) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *CancelTransaction) Validate() error { + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Multisig is not set") + } + if inst.AccountMetaSlice[1] == nil { + return errors.New("accounts.Transaction is not set") + } + if inst.AccountMetaSlice[2] == nil { + return errors.New("accounts.Member is not set") + } + if inst.AccountMetaSlice[3] == nil { + return errors.New("accounts.SystemProgram is not set") + } + } + return nil +} + +func (inst *CancelTransaction) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("CancelTransaction")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) + + // Accounts of the instruction: + instructionBranch.Child("Accounts[len=4]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" multisig", inst.AccountMetaSlice.Get(0))) + accountsBranch.Child(ag_format.Meta(" transaction", inst.AccountMetaSlice.Get(1))) + accountsBranch.Child(ag_format.Meta(" member", inst.AccountMetaSlice.Get(2))) + accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice.Get(3))) + }) + }) + }) +} + +func (obj CancelTransaction) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + return nil +} +func (obj *CancelTransaction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + return nil +} + +// NewCancelTransactionInstruction declares a new CancelTransaction instruction with the provided parameters and accounts. +func NewCancelTransactionInstruction( + // Accounts: + multisig ag_solanago.PublicKey, + transaction ag_solanago.PublicKey, + member ag_solanago.PublicKey, + systemProgram ag_solanago.PublicKey) *CancelTransaction { + return NewCancelTransactionInstructionBuilder(). + SetMultisigAccount(multisig). + SetTransactionAccount(transaction). + SetMemberAccount(member). + SetSystemProgramAccount(systemProgram) +} diff --git a/apps/solana/squads_mpl/CancelTransaction_test.go b/apps/solana/squads_mpl/CancelTransaction_test.go new file mode 100644 index 00000000..502a1a6c --- /dev/null +++ b/apps/solana/squads_mpl/CancelTransaction_test.go @@ -0,0 +1,32 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_CancelTransaction(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("CancelTransaction"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(CancelTransaction) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + got := new(CancelTransaction) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/apps/solana/squads_mpl/ChangeThreshold.go b/apps/solana/squads_mpl/ChangeThreshold.go new file mode 100644 index 00000000..1bf883d3 --- /dev/null +++ b/apps/solana/squads_mpl/ChangeThreshold.go @@ -0,0 +1,127 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "errors" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" +) + +// The instruction to change the threshold of the multisig +type ChangeThreshold struct { + NewThreshold *uint16 + + // [0] = [WRITE, SIGNER] multisig + ag_solanago.AccountMetaSlice `bin:"-"` +} + +// NewChangeThresholdInstructionBuilder creates a new `ChangeThreshold` instruction builder. +func NewChangeThresholdInstructionBuilder() *ChangeThreshold { + nd := &ChangeThreshold{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 1), + } + return nd +} + +// SetNewThreshold sets the "newThreshold" parameter. +func (inst *ChangeThreshold) SetNewThreshold(newThreshold uint16) *ChangeThreshold { + inst.NewThreshold = &newThreshold + return inst +} + +// SetMultisigAccount sets the "multisig" account. +func (inst *ChangeThreshold) SetMultisigAccount(multisig ag_solanago.PublicKey) *ChangeThreshold { + inst.AccountMetaSlice[0] = ag_solanago.Meta(multisig).WRITE().SIGNER() + return inst +} + +// GetMultisigAccount gets the "multisig" account. +func (inst *ChangeThreshold) GetMultisigAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(0) +} + +func (inst ChangeThreshold) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: Instruction_ChangeThreshold, + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst ChangeThreshold) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *ChangeThreshold) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.NewThreshold == nil { + return errors.New("NewThreshold parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Multisig is not set") + } + } + return nil +} + +func (inst *ChangeThreshold) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("ChangeThreshold")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("NewThreshold", *inst.NewThreshold)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts[len=1]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("multisig", inst.AccountMetaSlice.Get(0))) + }) + }) + }) +} + +func (obj ChangeThreshold) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `NewThreshold` param: + err = encoder.Encode(obj.NewThreshold) + if err != nil { + return err + } + return nil +} +func (obj *ChangeThreshold) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `NewThreshold`: + err = decoder.Decode(&obj.NewThreshold) + if err != nil { + return err + } + return nil +} + +// NewChangeThresholdInstruction declares a new ChangeThreshold instruction with the provided parameters and accounts. +func NewChangeThresholdInstruction( + // Parameters: + newThreshold uint16, + // Accounts: + multisig ag_solanago.PublicKey) *ChangeThreshold { + return NewChangeThresholdInstructionBuilder(). + SetNewThreshold(newThreshold). + SetMultisigAccount(multisig) +} diff --git a/apps/solana/squads_mpl/ChangeThreshold_test.go b/apps/solana/squads_mpl/ChangeThreshold_test.go new file mode 100644 index 00000000..1eb8d22b --- /dev/null +++ b/apps/solana/squads_mpl/ChangeThreshold_test.go @@ -0,0 +1,32 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_ChangeThreshold(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("ChangeThreshold"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(ChangeThreshold) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + got := new(ChangeThreshold) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/apps/solana/squads_mpl/Create.go b/apps/solana/squads_mpl/Create.go new file mode 100644 index 00000000..2611bdb3 --- /dev/null +++ b/apps/solana/squads_mpl/Create.go @@ -0,0 +1,234 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "errors" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" +) + +// Creates a new multisig account +type Create struct { + Threshold *uint16 + CreateKey *ag_solanago.PublicKey + Members *[]ag_solanago.PublicKey + Meta *string + + // [0] = [WRITE] multisig + // + // [1] = [WRITE, SIGNER] creator + // + // [2] = [] systemProgram + ag_solanago.AccountMetaSlice `bin:"-"` +} + +// NewCreateInstructionBuilder creates a new `Create` instruction builder. +func NewCreateInstructionBuilder() *Create { + nd := &Create{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3), + } + return nd +} + +// SetThreshold sets the "threshold" parameter. +func (inst *Create) SetThreshold(threshold uint16) *Create { + inst.Threshold = &threshold + return inst +} + +// SetCreateKey sets the "createKey" parameter. +func (inst *Create) SetCreateKey(createKey ag_solanago.PublicKey) *Create { + inst.CreateKey = &createKey + return inst +} + +// SetMembers sets the "members" parameter. +func (inst *Create) SetMembers(members []ag_solanago.PublicKey) *Create { + inst.Members = &members + return inst +} + +// SetMeta sets the "meta" parameter. +func (inst *Create) SetMeta(meta string) *Create { + inst.Meta = &meta + return inst +} + +// SetMultisigAccount sets the "multisig" account. +func (inst *Create) SetMultisigAccount(multisig ag_solanago.PublicKey) *Create { + inst.AccountMetaSlice[0] = ag_solanago.Meta(multisig).WRITE() + return inst +} + +// GetMultisigAccount gets the "multisig" account. +func (inst *Create) GetMultisigAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(0) +} + +// SetCreatorAccount sets the "creator" account. +func (inst *Create) SetCreatorAccount(creator ag_solanago.PublicKey) *Create { + inst.AccountMetaSlice[1] = ag_solanago.Meta(creator).WRITE().SIGNER() + return inst +} + +// GetCreatorAccount gets the "creator" account. +func (inst *Create) GetCreatorAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(1) +} + +// SetSystemProgramAccount sets the "systemProgram" account. +func (inst *Create) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *Create { + inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram) + return inst +} + +// GetSystemProgramAccount gets the "systemProgram" account. +func (inst *Create) GetSystemProgramAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(2) +} + +func (inst Create) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: Instruction_Create, + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst Create) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *Create) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.Threshold == nil { + return errors.New("Threshold parameter is not set") + } + if inst.CreateKey == nil { + return errors.New("CreateKey parameter is not set") + } + if inst.Members == nil { + return errors.New("Members parameter is not set") + } + if inst.Meta == nil { + return errors.New("Meta parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Multisig is not set") + } + if inst.AccountMetaSlice[1] == nil { + return errors.New("accounts.Creator is not set") + } + if inst.AccountMetaSlice[2] == nil { + return errors.New("accounts.SystemProgram is not set") + } + } + return nil +} + +func (inst *Create) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("Create")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params[len=4]").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("Threshold", *inst.Threshold)) + paramsBranch.Child(ag_format.Param("CreateKey", *inst.CreateKey)) + paramsBranch.Child(ag_format.Param(" Members", *inst.Members)) + paramsBranch.Child(ag_format.Param(" Meta", *inst.Meta)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" multisig", inst.AccountMetaSlice.Get(0))) + accountsBranch.Child(ag_format.Meta(" creator", inst.AccountMetaSlice.Get(1))) + accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice.Get(2))) + }) + }) + }) +} + +func (obj Create) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `Threshold` param: + err = encoder.Encode(obj.Threshold) + if err != nil { + return err + } + // Serialize `CreateKey` param: + err = encoder.Encode(obj.CreateKey) + if err != nil { + return err + } + // Serialize `Members` param: + err = encoder.Encode(obj.Members) + if err != nil { + return err + } + // Serialize `Meta` param: + err = encoder.Encode(obj.Meta) + if err != nil { + return err + } + return nil +} +func (obj *Create) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `Threshold`: + err = decoder.Decode(&obj.Threshold) + if err != nil { + return err + } + // Deserialize `CreateKey`: + err = decoder.Decode(&obj.CreateKey) + if err != nil { + return err + } + // Deserialize `Members`: + err = decoder.Decode(&obj.Members) + if err != nil { + return err + } + // Deserialize `Meta`: + err = decoder.Decode(&obj.Meta) + if err != nil { + return err + } + return nil +} + +// NewCreateInstruction declares a new Create instruction with the provided parameters and accounts. +func NewCreateInstruction( + // Parameters: + threshold uint16, + createKey ag_solanago.PublicKey, + members []ag_solanago.PublicKey, + meta string, + // Accounts: + multisig ag_solanago.PublicKey, + creator ag_solanago.PublicKey, + systemProgram ag_solanago.PublicKey) *Create { + return NewCreateInstructionBuilder(). + SetThreshold(threshold). + SetCreateKey(createKey). + SetMembers(members). + SetMeta(meta). + SetMultisigAccount(multisig). + SetCreatorAccount(creator). + SetSystemProgramAccount(systemProgram) +} diff --git a/apps/solana/squads_mpl/CreateTransaction.go b/apps/solana/squads_mpl/CreateTransaction.go new file mode 100644 index 00000000..4df641d8 --- /dev/null +++ b/apps/solana/squads_mpl/CreateTransaction.go @@ -0,0 +1,188 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "errors" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" +) + +// Instruction to create a multisig transaction. +// Each transaction is tied to a single authority, and must be specified when +// creating the instruction below. authority 0 is reserved for internal +// instructions, whereas authorities 1 or greater refer to a vault, +// upgrade authority, or other. +type CreateTransaction struct { + AuthorityIndex *uint32 + + // [0] = [WRITE] multisig + // + // [1] = [WRITE] transaction + // + // [2] = [WRITE, SIGNER] creator + // + // [3] = [] systemProgram + ag_solanago.AccountMetaSlice `bin:"-"` +} + +// NewCreateTransactionInstructionBuilder creates a new `CreateTransaction` instruction builder. +func NewCreateTransactionInstructionBuilder() *CreateTransaction { + nd := &CreateTransaction{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4), + } + return nd +} + +// SetAuthorityIndex sets the "authorityIndex" parameter. +func (inst *CreateTransaction) SetAuthorityIndex(authorityIndex uint32) *CreateTransaction { + inst.AuthorityIndex = &authorityIndex + return inst +} + +// SetMultisigAccount sets the "multisig" account. +func (inst *CreateTransaction) SetMultisigAccount(multisig ag_solanago.PublicKey) *CreateTransaction { + inst.AccountMetaSlice[0] = ag_solanago.Meta(multisig).WRITE() + return inst +} + +// GetMultisigAccount gets the "multisig" account. +func (inst *CreateTransaction) GetMultisigAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(0) +} + +// SetTransactionAccount sets the "transaction" account. +func (inst *CreateTransaction) SetTransactionAccount(transaction ag_solanago.PublicKey) *CreateTransaction { + inst.AccountMetaSlice[1] = ag_solanago.Meta(transaction).WRITE() + return inst +} + +// GetTransactionAccount gets the "transaction" account. +func (inst *CreateTransaction) GetTransactionAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(1) +} + +// SetCreatorAccount sets the "creator" account. +func (inst *CreateTransaction) SetCreatorAccount(creator ag_solanago.PublicKey) *CreateTransaction { + inst.AccountMetaSlice[2] = ag_solanago.Meta(creator).WRITE().SIGNER() + return inst +} + +// GetCreatorAccount gets the "creator" account. +func (inst *CreateTransaction) GetCreatorAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(2) +} + +// SetSystemProgramAccount sets the "systemProgram" account. +func (inst *CreateTransaction) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *CreateTransaction { + inst.AccountMetaSlice[3] = ag_solanago.Meta(systemProgram) + return inst +} + +// GetSystemProgramAccount gets the "systemProgram" account. +func (inst *CreateTransaction) GetSystemProgramAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(3) +} + +func (inst CreateTransaction) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: Instruction_CreateTransaction, + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst CreateTransaction) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *CreateTransaction) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.AuthorityIndex == nil { + return errors.New("AuthorityIndex parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Multisig is not set") + } + if inst.AccountMetaSlice[1] == nil { + return errors.New("accounts.Transaction is not set") + } + if inst.AccountMetaSlice[2] == nil { + return errors.New("accounts.Creator is not set") + } + if inst.AccountMetaSlice[3] == nil { + return errors.New("accounts.SystemProgram is not set") + } + } + return nil +} + +func (inst *CreateTransaction) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("CreateTransaction")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("AuthorityIndex", *inst.AuthorityIndex)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts[len=4]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" multisig", inst.AccountMetaSlice.Get(0))) + accountsBranch.Child(ag_format.Meta(" transaction", inst.AccountMetaSlice.Get(1))) + accountsBranch.Child(ag_format.Meta(" creator", inst.AccountMetaSlice.Get(2))) + accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice.Get(3))) + }) + }) + }) +} + +func (obj CreateTransaction) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `AuthorityIndex` param: + err = encoder.Encode(obj.AuthorityIndex) + if err != nil { + return err + } + return nil +} +func (obj *CreateTransaction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `AuthorityIndex`: + err = decoder.Decode(&obj.AuthorityIndex) + if err != nil { + return err + } + return nil +} + +// NewCreateTransactionInstruction declares a new CreateTransaction instruction with the provided parameters and accounts. +func NewCreateTransactionInstruction( + // Parameters: + authorityIndex uint32, + // Accounts: + multisig ag_solanago.PublicKey, + transaction ag_solanago.PublicKey, + creator ag_solanago.PublicKey, + systemProgram ag_solanago.PublicKey) *CreateTransaction { + return NewCreateTransactionInstructionBuilder(). + SetAuthorityIndex(authorityIndex). + SetMultisigAccount(multisig). + SetTransactionAccount(transaction). + SetCreatorAccount(creator). + SetSystemProgramAccount(systemProgram) +} diff --git a/apps/solana/squads_mpl/CreateTransaction_test.go b/apps/solana/squads_mpl/CreateTransaction_test.go new file mode 100644 index 00000000..808ba332 --- /dev/null +++ b/apps/solana/squads_mpl/CreateTransaction_test.go @@ -0,0 +1,32 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_CreateTransaction(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("CreateTransaction"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(CreateTransaction) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + got := new(CreateTransaction) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/apps/solana/squads_mpl/Create_test.go b/apps/solana/squads_mpl/Create_test.go new file mode 100644 index 00000000..2f48af9d --- /dev/null +++ b/apps/solana/squads_mpl/Create_test.go @@ -0,0 +1,32 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_Create(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("Create"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(Create) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + got := new(Create) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/apps/solana/squads_mpl/ExecuteInstruction.go b/apps/solana/squads_mpl/ExecuteInstruction.go new file mode 100644 index 00000000..32f48812 --- /dev/null +++ b/apps/solana/squads_mpl/ExecuteInstruction.go @@ -0,0 +1,167 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "errors" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" +) + +// Instruction to sequentially execute attached instructions. +// Instructions executed in this matter must be executed in order, +// this may be helpful for processing large batch transfers. +// This instruction can only be used for transactions with an authority +// index of 1 or greater. +// +// NOTE - do not use this instruction if there is not total clarity around +// potential side effects, as this instruction implies that the approved +// transaction will be executed partially, and potentially spread out over +// a period of time. This could introduce problems with state and failed +// transactions. For example: a program invoked in one of these instructions +// may be upgraded between executions and potentially leave one of the +// necessary accounts in an invalid state. +type ExecuteInstruction struct { + + // [0] = [WRITE] multisig + // + // [1] = [WRITE] transaction + // + // [2] = [WRITE] instruction + // + // [3] = [WRITE, SIGNER] member + ag_solanago.AccountMetaSlice `bin:"-"` +} + +// NewExecuteInstructionInstructionBuilder creates a new `ExecuteInstruction` instruction builder. +func NewExecuteInstructionInstructionBuilder() *ExecuteInstruction { + nd := &ExecuteInstruction{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4), + } + return nd +} + +// SetMultisigAccount sets the "multisig" account. +func (inst *ExecuteInstruction) SetMultisigAccount(multisig ag_solanago.PublicKey) *ExecuteInstruction { + inst.AccountMetaSlice[0] = ag_solanago.Meta(multisig).WRITE() + return inst +} + +// GetMultisigAccount gets the "multisig" account. +func (inst *ExecuteInstruction) GetMultisigAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(0) +} + +// SetTransactionAccount sets the "transaction" account. +func (inst *ExecuteInstruction) SetTransactionAccount(transaction ag_solanago.PublicKey) *ExecuteInstruction { + inst.AccountMetaSlice[1] = ag_solanago.Meta(transaction).WRITE() + return inst +} + +// GetTransactionAccount gets the "transaction" account. +func (inst *ExecuteInstruction) GetTransactionAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(1) +} + +// SetInstructionAccount sets the "instruction" account. +func (inst *ExecuteInstruction) SetInstructionAccount(instruction ag_solanago.PublicKey) *ExecuteInstruction { + inst.AccountMetaSlice[2] = ag_solanago.Meta(instruction).WRITE() + return inst +} + +// GetInstructionAccount gets the "instruction" account. +func (inst *ExecuteInstruction) GetInstructionAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(2) +} + +// SetMemberAccount sets the "member" account. +func (inst *ExecuteInstruction) SetMemberAccount(member ag_solanago.PublicKey) *ExecuteInstruction { + inst.AccountMetaSlice[3] = ag_solanago.Meta(member).WRITE().SIGNER() + return inst +} + +// GetMemberAccount gets the "member" account. +func (inst *ExecuteInstruction) GetMemberAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(3) +} + +func (inst ExecuteInstruction) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: Instruction_ExecuteInstruction, + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst ExecuteInstruction) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *ExecuteInstruction) Validate() error { + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Multisig is not set") + } + if inst.AccountMetaSlice[1] == nil { + return errors.New("accounts.Transaction is not set") + } + if inst.AccountMetaSlice[2] == nil { + return errors.New("accounts.Instruction is not set") + } + if inst.AccountMetaSlice[3] == nil { + return errors.New("accounts.Member is not set") + } + } + return nil +} + +func (inst *ExecuteInstruction) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("ExecuteInstruction")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) + + // Accounts of the instruction: + instructionBranch.Child("Accounts[len=4]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" multisig", inst.AccountMetaSlice.Get(0))) + accountsBranch.Child(ag_format.Meta("transaction", inst.AccountMetaSlice.Get(1))) + accountsBranch.Child(ag_format.Meta("instruction", inst.AccountMetaSlice.Get(2))) + accountsBranch.Child(ag_format.Meta(" member", inst.AccountMetaSlice.Get(3))) + }) + }) + }) +} + +func (obj ExecuteInstruction) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + return nil +} +func (obj *ExecuteInstruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + return nil +} + +// NewExecuteInstructionInstruction declares a new ExecuteInstruction instruction with the provided parameters and accounts. +func NewExecuteInstructionInstruction( + // Accounts: + multisig ag_solanago.PublicKey, + transaction ag_solanago.PublicKey, + instruction ag_solanago.PublicKey, + member ag_solanago.PublicKey) *ExecuteInstruction { + return NewExecuteInstructionInstructionBuilder(). + SetMultisigAccount(multisig). + SetTransactionAccount(transaction). + SetInstructionAccount(instruction). + SetMemberAccount(member) +} diff --git a/apps/solana/squads_mpl/ExecuteInstruction_test.go b/apps/solana/squads_mpl/ExecuteInstruction_test.go new file mode 100644 index 00000000..e815590c --- /dev/null +++ b/apps/solana/squads_mpl/ExecuteInstruction_test.go @@ -0,0 +1,32 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_ExecuteInstruction(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("ExecuteInstruction"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(ExecuteInstruction) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + got := new(ExecuteInstruction) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/apps/solana/squads_mpl/ExecuteTransaction.go b/apps/solana/squads_mpl/ExecuteTransaction.go new file mode 100644 index 00000000..994861df --- /dev/null +++ b/apps/solana/squads_mpl/ExecuteTransaction.go @@ -0,0 +1,170 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "errors" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" +) + +// Instruction to execute a transaction. +// Transaction status must be "executeReady", and the account list must match +// the unique indexed accounts in the following manner: +// [ix_1_account, ix_1_program_account, ix_1_remaining_account_1, ix_1_remaining_account_2, ...] +// +// Refer to the README for more information on how to construct the account list. +type ExecuteTransaction struct { + AccountList *[]byte + + // [0] = [WRITE] multisig + // + // [1] = [WRITE] transaction + // + // [2] = [WRITE, SIGNER] member + ag_solanago.AccountMetaSlice `bin:"-"` +} + +// NewExecuteTransactionInstructionBuilder creates a new `ExecuteTransaction` instruction builder. +func NewExecuteTransactionInstructionBuilder() *ExecuteTransaction { + nd := &ExecuteTransaction{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3), + } + return nd +} + +// SetAccountList sets the "accountList" parameter. +func (inst *ExecuteTransaction) SetAccountList(accountList []byte) *ExecuteTransaction { + inst.AccountList = &accountList + return inst +} + +// SetMultisigAccount sets the "multisig" account. +func (inst *ExecuteTransaction) SetMultisigAccount(multisig ag_solanago.PublicKey) *ExecuteTransaction { + inst.AccountMetaSlice[0] = ag_solanago.Meta(multisig).WRITE() + return inst +} + +// GetMultisigAccount gets the "multisig" account. +func (inst *ExecuteTransaction) GetMultisigAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(0) +} + +// SetTransactionAccount sets the "transaction" account. +func (inst *ExecuteTransaction) SetTransactionAccount(transaction ag_solanago.PublicKey) *ExecuteTransaction { + inst.AccountMetaSlice[1] = ag_solanago.Meta(transaction).WRITE() + return inst +} + +// GetTransactionAccount gets the "transaction" account. +func (inst *ExecuteTransaction) GetTransactionAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(1) +} + +// SetMemberAccount sets the "member" account. +func (inst *ExecuteTransaction) SetMemberAccount(member ag_solanago.PublicKey) *ExecuteTransaction { + inst.AccountMetaSlice[2] = ag_solanago.Meta(member).WRITE().SIGNER() + return inst +} + +// GetMemberAccount gets the "member" account. +func (inst *ExecuteTransaction) GetMemberAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(2) +} + +func (inst ExecuteTransaction) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: Instruction_ExecuteTransaction, + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst ExecuteTransaction) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *ExecuteTransaction) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.AccountList == nil { + return errors.New("AccountList parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Multisig is not set") + } + if inst.AccountMetaSlice[1] == nil { + return errors.New("accounts.Transaction is not set") + } + if inst.AccountMetaSlice[2] == nil { + return errors.New("accounts.Member is not set") + } + } + return nil +} + +func (inst *ExecuteTransaction) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("ExecuteTransaction")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("AccountList", *inst.AccountList)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" multisig", inst.AccountMetaSlice.Get(0))) + accountsBranch.Child(ag_format.Meta("transaction", inst.AccountMetaSlice.Get(1))) + accountsBranch.Child(ag_format.Meta(" member", inst.AccountMetaSlice.Get(2))) + }) + }) + }) +} + +func (obj ExecuteTransaction) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `AccountList` param: + err = encoder.Encode(obj.AccountList) + if err != nil { + return err + } + return nil +} +func (obj *ExecuteTransaction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `AccountList`: + err = decoder.Decode(&obj.AccountList) + if err != nil { + return err + } + return nil +} + +// NewExecuteTransactionInstruction declares a new ExecuteTransaction instruction with the provided parameters and accounts. +func NewExecuteTransactionInstruction( + // Parameters: + accountList []byte, + // Accounts: + multisig ag_solanago.PublicKey, + transaction ag_solanago.PublicKey, + member ag_solanago.PublicKey) *ExecuteTransaction { + return NewExecuteTransactionInstructionBuilder(). + SetAccountList(accountList). + SetMultisigAccount(multisig). + SetTransactionAccount(transaction). + SetMemberAccount(member) +} diff --git a/apps/solana/squads_mpl/ExecuteTransaction_test.go b/apps/solana/squads_mpl/ExecuteTransaction_test.go new file mode 100644 index 00000000..041f53ed --- /dev/null +++ b/apps/solana/squads_mpl/ExecuteTransaction_test.go @@ -0,0 +1,32 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_ExecuteTransaction(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("ExecuteTransaction"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(ExecuteTransaction) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + got := new(ExecuteTransaction) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/apps/solana/squads_mpl/RejectTransaction.go b/apps/solana/squads_mpl/RejectTransaction.go new file mode 100644 index 00000000..aa3c9251 --- /dev/null +++ b/apps/solana/squads_mpl/RejectTransaction.go @@ -0,0 +1,137 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "errors" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" +) + +// Instruction to reject a transaction. +// The transaction must have an "active" status. +type RejectTransaction struct { + + // [0] = [] multisig + // + // [1] = [WRITE] transaction + // + // [2] = [WRITE, SIGNER] member + ag_solanago.AccountMetaSlice `bin:"-"` +} + +// NewRejectTransactionInstructionBuilder creates a new `RejectTransaction` instruction builder. +func NewRejectTransactionInstructionBuilder() *RejectTransaction { + nd := &RejectTransaction{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3), + } + return nd +} + +// SetMultisigAccount sets the "multisig" account. +func (inst *RejectTransaction) SetMultisigAccount(multisig ag_solanago.PublicKey) *RejectTransaction { + inst.AccountMetaSlice[0] = ag_solanago.Meta(multisig) + return inst +} + +// GetMultisigAccount gets the "multisig" account. +func (inst *RejectTransaction) GetMultisigAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(0) +} + +// SetTransactionAccount sets the "transaction" account. +func (inst *RejectTransaction) SetTransactionAccount(transaction ag_solanago.PublicKey) *RejectTransaction { + inst.AccountMetaSlice[1] = ag_solanago.Meta(transaction).WRITE() + return inst +} + +// GetTransactionAccount gets the "transaction" account. +func (inst *RejectTransaction) GetTransactionAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(1) +} + +// SetMemberAccount sets the "member" account. +func (inst *RejectTransaction) SetMemberAccount(member ag_solanago.PublicKey) *RejectTransaction { + inst.AccountMetaSlice[2] = ag_solanago.Meta(member).WRITE().SIGNER() + return inst +} + +// GetMemberAccount gets the "member" account. +func (inst *RejectTransaction) GetMemberAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(2) +} + +func (inst RejectTransaction) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: Instruction_RejectTransaction, + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst RejectTransaction) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *RejectTransaction) Validate() error { + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Multisig is not set") + } + if inst.AccountMetaSlice[1] == nil { + return errors.New("accounts.Transaction is not set") + } + if inst.AccountMetaSlice[2] == nil { + return errors.New("accounts.Member is not set") + } + } + return nil +} + +func (inst *RejectTransaction) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("RejectTransaction")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) + + // Accounts of the instruction: + instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" multisig", inst.AccountMetaSlice.Get(0))) + accountsBranch.Child(ag_format.Meta("transaction", inst.AccountMetaSlice.Get(1))) + accountsBranch.Child(ag_format.Meta(" member", inst.AccountMetaSlice.Get(2))) + }) + }) + }) +} + +func (obj RejectTransaction) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + return nil +} +func (obj *RejectTransaction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + return nil +} + +// NewRejectTransactionInstruction declares a new RejectTransaction instruction with the provided parameters and accounts. +func NewRejectTransactionInstruction( + // Accounts: + multisig ag_solanago.PublicKey, + transaction ag_solanago.PublicKey, + member ag_solanago.PublicKey) *RejectTransaction { + return NewRejectTransactionInstructionBuilder(). + SetMultisigAccount(multisig). + SetTransactionAccount(transaction). + SetMemberAccount(member) +} diff --git a/apps/solana/squads_mpl/RejectTransaction_test.go b/apps/solana/squads_mpl/RejectTransaction_test.go new file mode 100644 index 00000000..f21d996b --- /dev/null +++ b/apps/solana/squads_mpl/RejectTransaction_test.go @@ -0,0 +1,32 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_RejectTransaction(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("RejectTransaction"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(RejectTransaction) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + got := new(RejectTransaction) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/apps/solana/squads_mpl/RemoveMember.go b/apps/solana/squads_mpl/RemoveMember.go new file mode 100644 index 00000000..2889d91f --- /dev/null +++ b/apps/solana/squads_mpl/RemoveMember.go @@ -0,0 +1,127 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "errors" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" +) + +// The instruction to remove a member from the multisig +type RemoveMember struct { + OldMember *ag_solanago.PublicKey + + // [0] = [WRITE, SIGNER] multisig + ag_solanago.AccountMetaSlice `bin:"-"` +} + +// NewRemoveMemberInstructionBuilder creates a new `RemoveMember` instruction builder. +func NewRemoveMemberInstructionBuilder() *RemoveMember { + nd := &RemoveMember{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 1), + } + return nd +} + +// SetOldMember sets the "oldMember" parameter. +func (inst *RemoveMember) SetOldMember(oldMember ag_solanago.PublicKey) *RemoveMember { + inst.OldMember = &oldMember + return inst +} + +// SetMultisigAccount sets the "multisig" account. +func (inst *RemoveMember) SetMultisigAccount(multisig ag_solanago.PublicKey) *RemoveMember { + inst.AccountMetaSlice[0] = ag_solanago.Meta(multisig).WRITE().SIGNER() + return inst +} + +// GetMultisigAccount gets the "multisig" account. +func (inst *RemoveMember) GetMultisigAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(0) +} + +func (inst RemoveMember) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: Instruction_RemoveMember, + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst RemoveMember) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *RemoveMember) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.OldMember == nil { + return errors.New("OldMember parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Multisig is not set") + } + } + return nil +} + +func (inst *RemoveMember) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("RemoveMember")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("OldMember", *inst.OldMember)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts[len=1]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("multisig", inst.AccountMetaSlice.Get(0))) + }) + }) + }) +} + +func (obj RemoveMember) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `OldMember` param: + err = encoder.Encode(obj.OldMember) + if err != nil { + return err + } + return nil +} +func (obj *RemoveMember) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `OldMember`: + err = decoder.Decode(&obj.OldMember) + if err != nil { + return err + } + return nil +} + +// NewRemoveMemberInstruction declares a new RemoveMember instruction with the provided parameters and accounts. +func NewRemoveMemberInstruction( + // Parameters: + oldMember ag_solanago.PublicKey, + // Accounts: + multisig ag_solanago.PublicKey) *RemoveMember { + return NewRemoveMemberInstructionBuilder(). + SetOldMember(oldMember). + SetMultisigAccount(multisig) +} diff --git a/apps/solana/squads_mpl/RemoveMemberAndChangeThreshold.go b/apps/solana/squads_mpl/RemoveMemberAndChangeThreshold.go new file mode 100644 index 00000000..a2ce2eac --- /dev/null +++ b/apps/solana/squads_mpl/RemoveMemberAndChangeThreshold.go @@ -0,0 +1,150 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "errors" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" +) + +// The instruction to change the threshold of the multisig and simultaneously remove a member +type RemoveMemberAndChangeThreshold struct { + OldMember *ag_solanago.PublicKey + NewThreshold *uint16 + + // [0] = [WRITE, SIGNER] multisig + ag_solanago.AccountMetaSlice `bin:"-"` +} + +// NewRemoveMemberAndChangeThresholdInstructionBuilder creates a new `RemoveMemberAndChangeThreshold` instruction builder. +func NewRemoveMemberAndChangeThresholdInstructionBuilder() *RemoveMemberAndChangeThreshold { + nd := &RemoveMemberAndChangeThreshold{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 1), + } + return nd +} + +// SetOldMember sets the "oldMember" parameter. +func (inst *RemoveMemberAndChangeThreshold) SetOldMember(oldMember ag_solanago.PublicKey) *RemoveMemberAndChangeThreshold { + inst.OldMember = &oldMember + return inst +} + +// SetNewThreshold sets the "newThreshold" parameter. +func (inst *RemoveMemberAndChangeThreshold) SetNewThreshold(newThreshold uint16) *RemoveMemberAndChangeThreshold { + inst.NewThreshold = &newThreshold + return inst +} + +// SetMultisigAccount sets the "multisig" account. +func (inst *RemoveMemberAndChangeThreshold) SetMultisigAccount(multisig ag_solanago.PublicKey) *RemoveMemberAndChangeThreshold { + inst.AccountMetaSlice[0] = ag_solanago.Meta(multisig).WRITE().SIGNER() + return inst +} + +// GetMultisigAccount gets the "multisig" account. +func (inst *RemoveMemberAndChangeThreshold) GetMultisigAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice.Get(0) +} + +func (inst RemoveMemberAndChangeThreshold) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: Instruction_RemoveMemberAndChangeThreshold, + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst RemoveMemberAndChangeThreshold) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *RemoveMemberAndChangeThreshold) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.OldMember == nil { + return errors.New("OldMember parameter is not set") + } + if inst.NewThreshold == nil { + return errors.New("NewThreshold parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Multisig is not set") + } + } + return nil +} + +func (inst *RemoveMemberAndChangeThreshold) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("RemoveMemberAndChangeThreshold")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params[len=2]").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param(" OldMember", *inst.OldMember)) + paramsBranch.Child(ag_format.Param("NewThreshold", *inst.NewThreshold)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts[len=1]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("multisig", inst.AccountMetaSlice.Get(0))) + }) + }) + }) +} + +func (obj RemoveMemberAndChangeThreshold) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `OldMember` param: + err = encoder.Encode(obj.OldMember) + if err != nil { + return err + } + // Serialize `NewThreshold` param: + err = encoder.Encode(obj.NewThreshold) + if err != nil { + return err + } + return nil +} +func (obj *RemoveMemberAndChangeThreshold) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `OldMember`: + err = decoder.Decode(&obj.OldMember) + if err != nil { + return err + } + // Deserialize `NewThreshold`: + err = decoder.Decode(&obj.NewThreshold) + if err != nil { + return err + } + return nil +} + +// NewRemoveMemberAndChangeThresholdInstruction declares a new RemoveMemberAndChangeThreshold instruction with the provided parameters and accounts. +func NewRemoveMemberAndChangeThresholdInstruction( + // Parameters: + oldMember ag_solanago.PublicKey, + newThreshold uint16, + // Accounts: + multisig ag_solanago.PublicKey) *RemoveMemberAndChangeThreshold { + return NewRemoveMemberAndChangeThresholdInstructionBuilder(). + SetOldMember(oldMember). + SetNewThreshold(newThreshold). + SetMultisigAccount(multisig) +} diff --git a/apps/solana/squads_mpl/RemoveMemberAndChangeThreshold_test.go b/apps/solana/squads_mpl/RemoveMemberAndChangeThreshold_test.go new file mode 100644 index 00000000..9c615a99 --- /dev/null +++ b/apps/solana/squads_mpl/RemoveMemberAndChangeThreshold_test.go @@ -0,0 +1,32 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_RemoveMemberAndChangeThreshold(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("RemoveMemberAndChangeThreshold"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(RemoveMemberAndChangeThreshold) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + got := new(RemoveMemberAndChangeThreshold) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/apps/solana/squads_mpl/RemoveMember_test.go b/apps/solana/squads_mpl/RemoveMember_test.go new file mode 100644 index 00000000..66958cb2 --- /dev/null +++ b/apps/solana/squads_mpl/RemoveMember_test.go @@ -0,0 +1,32 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_RemoveMember(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("RemoveMember"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(RemoveMember) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + got := new(RemoveMember) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/apps/solana/squads_mpl/accounts.go b/apps/solana/squads_mpl/accounts.go new file mode 100644 index 00000000..ee53dd6a --- /dev/null +++ b/apps/solana/squads_mpl/accounts.go @@ -0,0 +1,388 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "fmt" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" +) + +type Ms struct { + Threshold uint16 + AuthorityIndex uint16 + TransactionIndex uint32 + MsChangeIndex uint32 + Bump uint8 + CreateKey ag_solanago.PublicKey + AllowExternalExecute bool + Keys []ag_solanago.PublicKey +} + +var MsDiscriminator = [8]byte{70, 118, 9, 108, 254, 215, 31, 120} + +func (obj Ms) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Write account discriminator: + err = encoder.WriteBytes(MsDiscriminator[:], false) + if err != nil { + return err + } + // Serialize `Threshold` param: + err = encoder.Encode(obj.Threshold) + if err != nil { + return err + } + // Serialize `AuthorityIndex` param: + err = encoder.Encode(obj.AuthorityIndex) + if err != nil { + return err + } + // Serialize `TransactionIndex` param: + err = encoder.Encode(obj.TransactionIndex) + if err != nil { + return err + } + // Serialize `MsChangeIndex` param: + err = encoder.Encode(obj.MsChangeIndex) + if err != nil { + return err + } + // Serialize `Bump` param: + err = encoder.Encode(obj.Bump) + if err != nil { + return err + } + // Serialize `CreateKey` param: + err = encoder.Encode(obj.CreateKey) + if err != nil { + return err + } + // Serialize `AllowExternalExecute` param: + err = encoder.Encode(obj.AllowExternalExecute) + if err != nil { + return err + } + // Serialize `Keys` param: + err = encoder.Encode(obj.Keys) + if err != nil { + return err + } + return nil +} + +func (obj *Ms) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Read and check account discriminator: + { + discriminator, err := decoder.ReadTypeID() + if err != nil { + return err + } + if !discriminator.Equal(MsDiscriminator[:]) { + return fmt.Errorf( + "wrong discriminator: wanted %s, got %s", + "[70 118 9 108 254 215 31 120]", + fmt.Sprint(discriminator[:])) + } + } + // Deserialize `Threshold`: + err = decoder.Decode(&obj.Threshold) + if err != nil { + return err + } + // Deserialize `AuthorityIndex`: + err = decoder.Decode(&obj.AuthorityIndex) + if err != nil { + return err + } + // Deserialize `TransactionIndex`: + err = decoder.Decode(&obj.TransactionIndex) + if err != nil { + return err + } + // Deserialize `MsChangeIndex`: + err = decoder.Decode(&obj.MsChangeIndex) + if err != nil { + return err + } + // Deserialize `Bump`: + err = decoder.Decode(&obj.Bump) + if err != nil { + return err + } + // Deserialize `CreateKey`: + err = decoder.Decode(&obj.CreateKey) + if err != nil { + return err + } + // Deserialize `AllowExternalExecute`: + err = decoder.Decode(&obj.AllowExternalExecute) + if err != nil { + return err + } + // Deserialize `Keys`: + err = decoder.Decode(&obj.Keys) + if err != nil { + return err + } + return nil +} + +type MsTransaction struct { + Creator ag_solanago.PublicKey + Ms ag_solanago.PublicKey + TransactionIndex uint32 + AuthorityIndex uint32 + AuthorityBump uint8 + Status MsTransactionStatus + InstructionIndex uint8 + Bump uint8 + Approved []ag_solanago.PublicKey + Rejected []ag_solanago.PublicKey + Cancelled []ag_solanago.PublicKey + ExecutedIndex uint8 +} + +var MsTransactionDiscriminator = [8]byte{182, 151, 104, 216, 255, 1, 19, 157} + +func (obj MsTransaction) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Write account discriminator: + err = encoder.WriteBytes(MsTransactionDiscriminator[:], false) + if err != nil { + return err + } + // Serialize `Creator` param: + err = encoder.Encode(obj.Creator) + if err != nil { + return err + } + // Serialize `Ms` param: + err = encoder.Encode(obj.Ms) + if err != nil { + return err + } + // Serialize `TransactionIndex` param: + err = encoder.Encode(obj.TransactionIndex) + if err != nil { + return err + } + // Serialize `AuthorityIndex` param: + err = encoder.Encode(obj.AuthorityIndex) + if err != nil { + return err + } + // Serialize `AuthorityBump` param: + err = encoder.Encode(obj.AuthorityBump) + if err != nil { + return err + } + // Serialize `Status` param: + err = encoder.Encode(obj.Status) + if err != nil { + return err + } + // Serialize `InstructionIndex` param: + err = encoder.Encode(obj.InstructionIndex) + if err != nil { + return err + } + // Serialize `Bump` param: + err = encoder.Encode(obj.Bump) + if err != nil { + return err + } + // Serialize `Approved` param: + err = encoder.Encode(obj.Approved) + if err != nil { + return err + } + // Serialize `Rejected` param: + err = encoder.Encode(obj.Rejected) + if err != nil { + return err + } + // Serialize `Cancelled` param: + err = encoder.Encode(obj.Cancelled) + if err != nil { + return err + } + // Serialize `ExecutedIndex` param: + err = encoder.Encode(obj.ExecutedIndex) + if err != nil { + return err + } + return nil +} + +func (obj *MsTransaction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Read and check account discriminator: + { + discriminator, err := decoder.ReadTypeID() + if err != nil { + return err + } + if !discriminator.Equal(MsTransactionDiscriminator[:]) { + return fmt.Errorf( + "wrong discriminator: wanted %s, got %s", + "[182 151 104 216 255 1 19 157]", + fmt.Sprint(discriminator[:])) + } + } + // Deserialize `Creator`: + err = decoder.Decode(&obj.Creator) + if err != nil { + return err + } + // Deserialize `Ms`: + err = decoder.Decode(&obj.Ms) + if err != nil { + return err + } + // Deserialize `TransactionIndex`: + err = decoder.Decode(&obj.TransactionIndex) + if err != nil { + return err + } + // Deserialize `AuthorityIndex`: + err = decoder.Decode(&obj.AuthorityIndex) + if err != nil { + return err + } + // Deserialize `AuthorityBump`: + err = decoder.Decode(&obj.AuthorityBump) + if err != nil { + return err + } + // Deserialize `Status`: + err = decoder.Decode(&obj.Status) + if err != nil { + return err + } + // Deserialize `InstructionIndex`: + err = decoder.Decode(&obj.InstructionIndex) + if err != nil { + return err + } + // Deserialize `Bump`: + err = decoder.Decode(&obj.Bump) + if err != nil { + return err + } + // Deserialize `Approved`: + err = decoder.Decode(&obj.Approved) + if err != nil { + return err + } + // Deserialize `Rejected`: + err = decoder.Decode(&obj.Rejected) + if err != nil { + return err + } + // Deserialize `Cancelled`: + err = decoder.Decode(&obj.Cancelled) + if err != nil { + return err + } + // Deserialize `ExecutedIndex`: + err = decoder.Decode(&obj.ExecutedIndex) + if err != nil { + return err + } + return nil +} + +type MsInstruction struct { + ProgramId ag_solanago.PublicKey + Keys []MsAccountMeta + Data []byte + InstructionIndex uint8 + Bump uint8 + Executed bool +} + +var MsInstructionDiscriminator = [8]byte{238, 185, 126, 149, 189, 89, 255, 92} + +func (obj MsInstruction) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Write account discriminator: + err = encoder.WriteBytes(MsInstructionDiscriminator[:], false) + if err != nil { + return err + } + // Serialize `ProgramId` param: + err = encoder.Encode(obj.ProgramId) + if err != nil { + return err + } + // Serialize `Keys` param: + err = encoder.Encode(obj.Keys) + if err != nil { + return err + } + // Serialize `Data` param: + err = encoder.Encode(obj.Data) + if err != nil { + return err + } + // Serialize `InstructionIndex` param: + err = encoder.Encode(obj.InstructionIndex) + if err != nil { + return err + } + // Serialize `Bump` param: + err = encoder.Encode(obj.Bump) + if err != nil { + return err + } + // Serialize `Executed` param: + err = encoder.Encode(obj.Executed) + if err != nil { + return err + } + return nil +} + +func (obj *MsInstruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Read and check account discriminator: + { + discriminator, err := decoder.ReadTypeID() + if err != nil { + return err + } + if !discriminator.Equal(MsInstructionDiscriminator[:]) { + return fmt.Errorf( + "wrong discriminator: wanted %s, got %s", + "[238 185 126 149 189 89 255 92]", + fmt.Sprint(discriminator[:])) + } + } + // Deserialize `ProgramId`: + err = decoder.Decode(&obj.ProgramId) + if err != nil { + return err + } + // Deserialize `Keys`: + err = decoder.Decode(&obj.Keys) + if err != nil { + return err + } + // Deserialize `Data`: + err = decoder.Decode(&obj.Data) + if err != nil { + return err + } + // Deserialize `InstructionIndex`: + err = decoder.Decode(&obj.InstructionIndex) + if err != nil { + return err + } + // Deserialize `Bump`: + err = decoder.Decode(&obj.Bump) + if err != nil { + return err + } + // Deserialize `Executed`: + err = decoder.Decode(&obj.Executed) + if err != nil { + return err + } + return nil +} diff --git a/apps/solana/squads_mpl/instructions.go b/apps/solana/squads_mpl/instructions.go new file mode 100644 index 00000000..e8d63b06 --- /dev/null +++ b/apps/solana/squads_mpl/instructions.go @@ -0,0 +1,268 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "bytes" + "fmt" + ag_spew "github.com/davecgh/go-spew/spew" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_text "github.com/gagliardetto/solana-go/text" + ag_treeout "github.com/gagliardetto/treeout" +) + +var ProgramID ag_solanago.PublicKey + +func SetProgramID(pubkey ag_solanago.PublicKey) { + ProgramID = pubkey + ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction) +} + +const ProgramName = "SquadsMpl" + +func init() { + if !ProgramID.IsZero() { + ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction) + } +} + +var ( + // Creates a new multisig account + Instruction_Create = ag_binary.TypeID([8]byte{24, 30, 200, 40, 5, 28, 7, 119}) + + // The instruction to add a new member to the multisig. + // Adds member/key to the multisig and reallocates space if neccessary + // If the multisig needs to be reallocated, it must be prefunded with + // enough lamports to cover the new size. + Instruction_AddMember = ag_binary.TypeID([8]byte{13, 116, 123, 130, 126, 198, 57, 34}) + + // The instruction to remove a member from the multisig + Instruction_RemoveMember = ag_binary.TypeID([8]byte{171, 57, 231, 150, 167, 128, 18, 55}) + + // The instruction to change the threshold of the multisig and simultaneously remove a member + Instruction_RemoveMemberAndChangeThreshold = ag_binary.TypeID([8]byte{230, 97, 183, 248, 43, 190, 154, 29}) + + // The instruction to change the threshold of the multisig and simultaneously add a member + Instruction_AddMemberAndChangeThreshold = ag_binary.TypeID([8]byte{114, 213, 59, 47, 214, 157, 150, 170}) + + // The instruction to change the threshold of the multisig + Instruction_ChangeThreshold = ag_binary.TypeID([8]byte{146, 151, 213, 63, 121, 79, 9, 29}) + + // instruction to increase the authority value tracked in the multisig + // This is optional, as authorities are simply PDAs, however it may be helpful + // to keep track of commonly used authorities in a UI. + // This has no functional impact on the multisig or its functionality, but + // can be used to track commonly used authorities (ie, vault 1, vault 2, etc.) + Instruction_AddAuthority = ag_binary.TypeID([8]byte{229, 9, 106, 73, 91, 213, 109, 183}) + + // Instruction to create a multisig transaction. + // Each transaction is tied to a single authority, and must be specified when + // creating the instruction below. authority 0 is reserved for internal + // instructions, whereas authorities 1 or greater refer to a vault, + // upgrade authority, or other. + Instruction_CreateTransaction = ag_binary.TypeID([8]byte{227, 193, 53, 239, 55, 126, 112, 105}) + + // Instruction to set the state of a transaction "active". + // "active" transactions can then be signed off by multisig members + Instruction_ActivateTransaction = ag_binary.TypeID([8]byte{56, 17, 0, 163, 135, 11, 135, 32}) + + // Instruction to attach an instruction to a transaction. + // Transactions must be in the "draft" status, and any + // signer (aside from execution payer) specified in an instruction + // must match the authority PDA specified during the transaction creation. + Instruction_AddInstruction = ag_binary.TypeID([8]byte{11, 70, 136, 166, 202, 55, 246, 74}) + + // Instruction to approve a transaction on behalf of a member. + // The transaction must have an "active" status + Instruction_ApproveTransaction = ag_binary.TypeID([8]byte{224, 39, 88, 181, 36, 59, 155, 122}) + + // Instruction to reject a transaction. + // The transaction must have an "active" status. + Instruction_RejectTransaction = ag_binary.TypeID([8]byte{47, 141, 218, 192, 80, 97, 209, 116}) + + // Instruction to cancel a transaction. + // Transactions must be in the "executeReady" status. + // Transaction will only be cancelled if the number of + // cancellations reaches the threshold. A cancelled + // transaction will no longer be able to be executed. + Instruction_CancelTransaction = ag_binary.TypeID([8]byte{65, 191, 19, 127, 230, 26, 214, 142}) + + // Instruction to execute a transaction. + // Transaction status must be "executeReady", and the account list must match + // the unique indexed accounts in the following manner: + // [ix_1_account, ix_1_program_account, ix_1_remaining_account_1, ix_1_remaining_account_2, ...] + // + // Refer to the README for more information on how to construct the account list. + Instruction_ExecuteTransaction = ag_binary.TypeID([8]byte{231, 173, 49, 91, 235, 24, 68, 19}) + + // Instruction to sequentially execute attached instructions. + // Instructions executed in this matter must be executed in order, + // this may be helpful for processing large batch transfers. + // This instruction can only be used for transactions with an authority + // index of 1 or greater. + // + // NOTE - do not use this instruction if there is not total clarity around + // potential side effects, as this instruction implies that the approved + // transaction will be executed partially, and potentially spread out over + // a period of time. This could introduce problems with state and failed + // transactions. For example: a program invoked in one of these instructions + // may be upgraded between executions and potentially leave one of the + // necessary accounts in an invalid state. + Instruction_ExecuteInstruction = ag_binary.TypeID([8]byte{48, 18, 40, 40, 75, 74, 147, 110}) +) + +// InstructionIDToName returns the name of the instruction given its ID. +func InstructionIDToName(id ag_binary.TypeID) string { + switch id { + case Instruction_Create: + return "Create" + case Instruction_AddMember: + return "AddMember" + case Instruction_RemoveMember: + return "RemoveMember" + case Instruction_RemoveMemberAndChangeThreshold: + return "RemoveMemberAndChangeThreshold" + case Instruction_AddMemberAndChangeThreshold: + return "AddMemberAndChangeThreshold" + case Instruction_ChangeThreshold: + return "ChangeThreshold" + case Instruction_AddAuthority: + return "AddAuthority" + case Instruction_CreateTransaction: + return "CreateTransaction" + case Instruction_ActivateTransaction: + return "ActivateTransaction" + case Instruction_AddInstruction: + return "AddInstruction" + case Instruction_ApproveTransaction: + return "ApproveTransaction" + case Instruction_RejectTransaction: + return "RejectTransaction" + case Instruction_CancelTransaction: + return "CancelTransaction" + case Instruction_ExecuteTransaction: + return "ExecuteTransaction" + case Instruction_ExecuteInstruction: + return "ExecuteInstruction" + default: + return "" + } +} + +type Instruction struct { + ag_binary.BaseVariant +} + +func (inst *Instruction) EncodeToTree(parent ag_treeout.Branches) { + if enToTree, ok := inst.Impl.(ag_text.EncodableToTree); ok { + enToTree.EncodeToTree(parent) + } else { + parent.Child(ag_spew.Sdump(inst)) + } +} + +var InstructionImplDef = ag_binary.NewVariantDefinition( + ag_binary.AnchorTypeIDEncoding, + []ag_binary.VariantType{ + { + "create", (*Create)(nil), + }, + { + "add_member", (*AddMember)(nil), + }, + { + "remove_member", (*RemoveMember)(nil), + }, + { + "remove_member_and_change_threshold", (*RemoveMemberAndChangeThreshold)(nil), + }, + { + "add_member_and_change_threshold", (*AddMemberAndChangeThreshold)(nil), + }, + { + "change_threshold", (*ChangeThreshold)(nil), + }, + { + "add_authority", (*AddAuthority)(nil), + }, + { + "create_transaction", (*CreateTransaction)(nil), + }, + { + "activate_transaction", (*ActivateTransaction)(nil), + }, + { + "add_instruction", (*AddInstruction)(nil), + }, + { + "approve_transaction", (*ApproveTransaction)(nil), + }, + { + "reject_transaction", (*RejectTransaction)(nil), + }, + { + "cancel_transaction", (*CancelTransaction)(nil), + }, + { + "execute_transaction", (*ExecuteTransaction)(nil), + }, + { + "execute_instruction", (*ExecuteInstruction)(nil), + }, + }, +) + +func (inst *Instruction) ProgramID() ag_solanago.PublicKey { + return ProgramID +} + +func (inst *Instruction) Accounts() (out []*ag_solanago.AccountMeta) { + return inst.Impl.(ag_solanago.AccountsGettable).GetAccounts() +} + +func (inst *Instruction) Data() ([]byte, error) { + buf := new(bytes.Buffer) + if err := ag_binary.NewBorshEncoder(buf).Encode(inst); err != nil { + return nil, fmt.Errorf("unable to encode instruction: %w", err) + } + return buf.Bytes(), nil +} + +func (inst *Instruction) TextEncode(encoder *ag_text.Encoder, option *ag_text.Option) error { + return encoder.Encode(inst.Impl, option) +} + +func (inst *Instruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error { + return inst.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef) +} + +func (inst *Instruction) MarshalWithEncoder(encoder *ag_binary.Encoder) error { + err := encoder.WriteBytes(inst.TypeID.Bytes(), false) + if err != nil { + return fmt.Errorf("unable to write variant type: %w", err) + } + return encoder.Encode(inst.Impl) +} + +func registryDecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (interface{}, error) { + inst, err := DecodeInstruction(accounts, data) + if err != nil { + return nil, err + } + return inst, nil +} + +func DecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (*Instruction, error) { + inst := new(Instruction) + if err := ag_binary.NewBorshDecoder(data).Decode(inst); err != nil { + return nil, fmt.Errorf("unable to decode instruction: %w", err) + } + if v, ok := inst.Impl.(ag_solanago.AccountsSettable); ok { + err := v.SetAccounts(accounts) + if err != nil { + return nil, fmt.Errorf("unable to set accounts for instruction: %w", err) + } + } + return inst, nil +} diff --git a/apps/solana/squads_mpl/testing_utils.go b/apps/solana/squads_mpl/testing_utils.go new file mode 100644 index 00000000..f2901524 --- /dev/null +++ b/apps/solana/squads_mpl/testing_utils.go @@ -0,0 +1,20 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + "bytes" + "fmt" + ag_binary "github.com/gagliardetto/binary" +) + +func encodeT(data interface{}, buf *bytes.Buffer) error { + if err := ag_binary.NewBorshEncoder(buf).Encode(data); err != nil { + return fmt.Errorf("unable to encode instruction: %w", err) + } + return nil +} + +func decodeT(dst interface{}, data []byte) error { + return ag_binary.NewBorshDecoder(data).Decode(dst) +} diff --git a/apps/solana/squads_mpl/types.go b/apps/solana/squads_mpl/types.go new file mode 100644 index 00000000..8f39b316 --- /dev/null +++ b/apps/solana/squads_mpl/types.go @@ -0,0 +1,126 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package squads_mpl + +import ( + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" +) + +type MsAccountMeta struct { + Pubkey ag_solanago.PublicKey + IsSigner bool + IsWritable bool +} + +func (obj MsAccountMeta) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `Pubkey` param: + err = encoder.Encode(obj.Pubkey) + if err != nil { + return err + } + // Serialize `IsSigner` param: + err = encoder.Encode(obj.IsSigner) + if err != nil { + return err + } + // Serialize `IsWritable` param: + err = encoder.Encode(obj.IsWritable) + if err != nil { + return err + } + return nil +} + +func (obj *MsAccountMeta) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `Pubkey`: + err = decoder.Decode(&obj.Pubkey) + if err != nil { + return err + } + // Deserialize `IsSigner`: + err = decoder.Decode(&obj.IsSigner) + if err != nil { + return err + } + // Deserialize `IsWritable`: + err = decoder.Decode(&obj.IsWritable) + if err != nil { + return err + } + return nil +} + +type IncomingInstruction struct { + ProgramId ag_solanago.PublicKey + Keys []MsAccountMeta + Data []byte +} + +func (obj IncomingInstruction) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `ProgramId` param: + err = encoder.Encode(obj.ProgramId) + if err != nil { + return err + } + // Serialize `Keys` param: + err = encoder.Encode(obj.Keys) + if err != nil { + return err + } + // Serialize `Data` param: + err = encoder.Encode(obj.Data) + if err != nil { + return err + } + return nil +} + +func (obj *IncomingInstruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `ProgramId`: + err = decoder.Decode(&obj.ProgramId) + if err != nil { + return err + } + // Deserialize `Keys`: + err = decoder.Decode(&obj.Keys) + if err != nil { + return err + } + // Deserialize `Data`: + err = decoder.Decode(&obj.Data) + if err != nil { + return err + } + return nil +} + +type MsTransactionStatus ag_binary.BorshEnum + +const ( + MsTransactionStatusDraft MsTransactionStatus = iota + MsTransactionStatusActive + MsTransactionStatusExecuteReady + MsTransactionStatusExecuted + MsTransactionStatusRejected + MsTransactionStatusCancelled +) + +func (value MsTransactionStatus) String() string { + switch value { + case MsTransactionStatusDraft: + return "Draft" + case MsTransactionStatusActive: + return "Active" + case MsTransactionStatusExecuteReady: + return "ExecuteReady" + case MsTransactionStatusExecuted: + return "Executed" + case MsTransactionStatusRejected: + return "Rejected" + case MsTransactionStatusCancelled: + return "Cancelled" + default: + return "" + } +} diff --git a/apps/solana/transaction.go b/apps/solana/transaction.go new file mode 100644 index 00000000..5131356e --- /dev/null +++ b/apps/solana/transaction.go @@ -0,0 +1,573 @@ +package solana + +import ( + "context" + "fmt" + "io" + "math/big" + "slices" + + "github.com/MixinNetwork/mixin/common" + "github.com/MixinNetwork/safe/apps/solana/squads_mpl" + solana "github.com/gagliardetto/solana-go" + ata "github.com/gagliardetto/solana-go/programs/associated-token-account" + "github.com/gagliardetto/solana-go/programs/system" + token "github.com/gagliardetto/solana-go/programs/token" + "github.com/gagliardetto/solana-go/rpc" + confirm "github.com/gagliardetto/solana-go/rpc/sendAndConfirmTransaction" + "github.com/gofrs/uuid/v5" +) + +type Output struct { + TokenAddress string `json:"token_address"` + Destination string `json:"destination"` + Amount *big.Int `json:"amount"` +} + +func ExtractOutputs(tx *solana.Transaction) []*Output { + var ( + outputs []*Output + tokenAccounts = make(map[solana.PublicKey]token.Account) + ) + + for _, ix := range tx.Message.Instructions { + programID, err := tx.Message.Program(ix.ProgramIDIndex) + if err != nil { + panic(err) + } + + accounts, err := ix.ResolveInstructionAccounts(&tx.Message) + if err != nil { + panic(err) + } + + if programID == ata.ProgramID { + // inst must be createIdempotent + if len(ix.Data) == 0 || ix.Data[0] != 1 { + panic(fmt.Sprintf("ata: invalid createIdempotent instruction: %+v", ix)) + } + + inst, err := ata.DecodeInstruction(accounts, ix.Data) + if err != nil { + panic(err) + } + + create, ok := inst.Impl.(*ata.Create) + if !ok { + panic(fmt.Sprintf("ata: invalid create instruction: %+v", inst)) + } + + tokenAccounts[create.AccountMetaSlice[1].PublicKey] = token.Account{ + Mint: create.Mint, + Owner: create.Wallet, + } + + continue + } + + if programID != squads_mpl.ProgramID { + continue + } + + inst, err := squads_mpl.DecodeInstruction(accounts, ix.Data) + if err != nil { + panic(err) + } + + addInst, ok := inst.Impl.(*squads_mpl.AddInstruction) + if !ok { + continue + } + + multisigAccount := addInst.GetMultisigAccount() + authority := GetDefaultAuthorityPDA(multisigAccount.PublicKey) + + // clear accounts + accounts = make(solana.AccountMetaSlice, len(addInst.IncomingInstruction.Keys)) + for i, key := range addInst.IncomingInstruction.Keys { + accounts[i] = &solana.AccountMeta{ + PublicKey: key.Pubkey, + IsWritable: key.IsWritable, + IsSigner: key.IsSigner, + } + } + + switch addInst.IncomingInstruction.ProgramId { + case system.ProgramID: + inst, err := system.DecodeInstruction(accounts, addInst.IncomingInstruction.Data) + if err != nil { + panic(err) + } + + transfer, ok := inst.Impl.(*system.Transfer) + if !ok { + panic(fmt.Sprintf("system: invalid transfer instruction: %+v", inst)) + } + + if transfer.GetFundingAccount().PublicKey != authority { + panic(fmt.Sprintf("system: invalid transfer instruction: %+v", inst)) + } + + outputs = append(outputs, &Output{ + TokenAddress: SolanaEmptyAddress, + Destination: transfer.GetRecipientAccount().PublicKey.String(), + Amount: new(big.Int).SetUint64(*transfer.Lamports), + }) + case token.ProgramID: + inst, err := token.DecodeInstruction(accounts, addInst.IncomingInstruction.Data) + if err != nil { + panic(err) + } + + transfer, ok := inst.Impl.(*token.Transfer) + if !ok { + panic(fmt.Sprintf("token: invalid transfer instruction: %+v", inst)) + } + + destination, ok := tokenAccounts[transfer.GetDestinationAccount().PublicKey] + if !ok { + panic(fmt.Sprintf("token: invalid transfer instruction: %+v", inst)) + } + + // validate source account + if from, _, _ := solana.FindAssociatedTokenAddress(authority, destination.Mint); from != transfer.GetSourceAccount().PublicKey { + panic(fmt.Sprintf("token: invalid transfer instruction: %+v", inst)) + } + + outputs = append(outputs, &Output{ + TokenAddress: destination.Mint.String(), + Destination: destination.Owner.String(), + Amount: new(big.Int).SetUint64(*transfer.Amount), + }) + } + } + + return outputs +} + +type BuildSquadsSafeParams struct { + Members []solana.PublicKey + Creator solana.PublicKey + Nonce solana.PublicKey + BlockHash solana.Hash + Payer solana.PublicKey + Threshold uint16 +} + +func BuildSquadsSafe(params BuildSquadsSafeParams) *solana.Transaction { + tx, err := solana.NewTransaction( + []solana.Instruction{ + system.NewAdvanceNonceAccountInstruction( + params.Nonce, + solana.SysVarRecentBlockHashesPubkey, + params.Payer, + ).Build(), + squads_mpl.NewCreateInstruction( + params.Threshold, + params.Creator, + params.Members, + "", + GetMultisigPDA(params.Creator), + params.Creator, + system.ProgramID, + ).Build(), + }, + params.BlockHash, + solana.TransactionPayer(params.Payer), + ) + if err != nil { + panic(fmt.Errorf("solana.NewTransaction() => %v", err)) + } + + return tx +} + +type TransactionRequest struct { + Flag uint8 + RequestID uuid.UUID + NonceAccount solana.PublicKey + BlockHash solana.Hash + PayerAccount solana.PublicKey + + // extra is the extra data for the transaction + // 1. transaction reference to a storage transaction + // 2. destination address + Extra [32]byte +} + +func DecodeTransactionRequest(b []byte) (*TransactionRequest, error) { + var ( + dec = common.NewDecoder(b) + req TransactionRequest + ) + + if b, err := dec.ReadByte(); err != nil { + return nil, err + } else { + req.Flag = b + } + + if err := dec.Read(req.RequestID[:]); err != nil { + return nil, err + } + + if err := dec.Read(req.NonceAccount[:]); err != nil { + return nil, err + } + + if err := dec.Read(req.BlockHash[:]); err != nil { + return nil, err + } + + if err := dec.Read(req.PayerAccount[:]); err != nil { + return nil, err + } + + if err := dec.Read(req.Extra[:]); err != nil { + return nil, err + } + + // it should be io.EOF + if _, err := dec.ReadByte(); err != io.EOF { + return nil, fmt.Errorf("invalid transaction request length") + } + + return &req, nil +} + +// CreateTransactionFromOutputs creates a transaction from outputs +// `creator` is the creator of the multisig & the transaction, used to be the holder of the safe +func CreateTransactionFromOutputs(req *TransactionRequest, outputs []*Output, voters []solana.PublicKey, creator solana.PublicKey, nonce uint32) (*solana.Transaction, error) { + instructions := []solana.Instruction{ + system.NewAdvanceNonceAccountInstruction( + req.NonceAccount, + solana.SysVarRecentBlockHashesPubkey, + req.PayerAccount, + ).Build(), + } + + msPda := GetMultisigPDA(creator) + authorityPad := GetDefaultAuthorityPDA(msPda) + txPda := GetTransactionPDA(msPda, nonce) + + // 1. create transaction + instructions = append(instructions, squads_mpl.NewCreateTransactionInstruction( + DefaultAuthorityIndex, + msPda, + txPda, + creator, + system.ProgramID, + ).Build()) + + // 2. add transfer instructions + var ( + innerInstructions []solana.Instruction + tokenAccounts = make(map[solana.PublicKey]struct{}) + ) + + for _, output := range outputs { + // native solana + if output.TokenAddress == SolanaEmptyAddress { + innerInstructions = append(innerInstructions, system.NewTransferInstruction( + output.Amount.Uint64(), + authorityPad, + solana.MPK(output.Destination), + ).Build()) + + continue + } + + mint := solana.MPK(output.TokenAddress) + source, _, err := solana.FindAssociatedTokenAddress(authorityPad, mint) + if err != nil { + return nil, fmt.Errorf("solana.FindAssociatedTokenAddress() => %v", err) + } + + destination, _, err := solana.FindAssociatedTokenAddress(solana.MPK(output.Destination), mint) + if err != nil { + return nil, fmt.Errorf("solana.FindAssociatedTokenAddress() => %v", err) + } + + innerInstructions = append(innerInstructions, token.NewTransferInstruction( + output.Amount.Uint64(), + source, + destination, + authorityPad, + nil, + ).Build()) + + if _, ok := tokenAccounts[destination]; !ok { + inst := ata.NewCreateInstruction(req.PayerAccount, solana.MPK(output.Destination), mint).Build() + // createIdempotent + instructions = append(instructions, solana.NewInstruction(inst.ProgramID(), inst.Accounts(), []byte{1})) + tokenAccounts[destination] = struct{}{} + } + } + + var ixKeysList solana.AccountMetaSlice + + for idx, inner := range innerInstructions { + if idx > 255 { + return nil, fmt.Errorf("too many instructions") + } + + data, err := inner.Data() + if err != nil { + return nil, fmt.Errorf("solana.Instruction.Data() => %v", err) + } + + ixKey := GetInstructionPDA(txPda, uint8(idx+1)) + ixKeysList.Append(solana.Meta(ixKey)) + ixKeysList.Append(solana.Meta(inner.ProgramID())) + + var keys []squads_mpl.MsAccountMeta + for _, account := range inner.Accounts() { + keys = append(keys, squads_mpl.MsAccountMeta{ + Pubkey: account.PublicKey, + IsSigner: account.IsSigner, + IsWritable: account.IsWritable, + }) + + ixKeysList.Append(account) + } + + instructions = append(instructions, squads_mpl.NewAddInstructionInstruction( + squads_mpl.IncomingInstruction{ + ProgramId: inner.ProgramID(), + Keys: keys, + Data: data, + }, + msPda, + txPda, + ixKey, + creator, + system.ProgramID, + ).Build()) + } + + // active transaction + instructions = append(instructions, squads_mpl.NewActivateTransactionInstruction( + msPda, + txPda, + creator, + ).Build()) + + // vote + for _, voter := range voters { + instructions = append(instructions, squads_mpl.NewApproveTransactionInstruction( + msPda, + txPda, + voter, + ).Build()) + } + + var ( + keysUnique solana.AccountMetaSlice + keyIndexMap = make([]byte, len(ixKeysList)) + ) + + for idx, key := range ixKeysList { + p := slices.IndexFunc(keysUnique, func(k *solana.AccountMeta) bool { + return k.PublicKey.Equals(key.PublicKey) && k.IsWritable == key.IsWritable + }) + + if p < 0 { + keysUnique.Append(key) + p = len(keysUnique) - 1 + } + + if p > 255 { + return nil, fmt.Errorf("too many unique keys") + } + + keyIndexMap[idx] = byte(p) + } + + // execute + executeIxBuilder := squads_mpl.NewExecuteTransactionInstruction(keyIndexMap, msPda, txPda, creator) + for _, key := range keysUnique { + executeIxBuilder.Append(key) + } + + instructions = append(instructions, executeIxBuilder.Build()) + return solana.NewTransaction(instructions, req.BlockHash, solana.TransactionPayer(req.PayerAccount)) +} + +const nonceAccountSize uint64 = 80 + +func (c *Client) ReadNonceAccount(ctx context.Context, nonceKey solana.PublicKey) (*system.NonceAccount, error) { + client := c.getRPCClient() + var account system.NonceAccount + if err := client.GetAccountDataInto(ctx, nonceKey, &account); err != nil { + return nil, fmt.Errorf("solana.GetAccountInfo() => %v", err) + } + return &account, nil +} + +func (c *Client) CreateNonceAccount(ctx context.Context, nonceKey, payer solana.PrivateKey) (*system.NonceAccount, error) { + client := c.getRPCClient() + blockhash, err := client.GetLatestBlockhash(ctx, rpc.CommitmentFinalized) + if err != nil { + return nil, fmt.Errorf("solana.GetLatestBlockhash() => %v", err) + } + + // 计算 Nonce Account 所需的最小 lamports + // system.NonceAccountSize = 80 bytes + rentExemptBalance, err := client.GetMinimumBalanceForRentExemption( + ctx, + nonceAccountSize, + rpc.CommitmentFinalized, + ) + if err != nil { + return nil, fmt.Errorf("failed to get rent exempt balance: %w", err) + } + + tx, err := solana.NewTransaction( + []solana.Instruction{ + system.NewCreateAccountInstruction( + rentExemptBalance, + nonceAccountSize, + system.ProgramID, + payer.PublicKey(), + nonceKey.PublicKey(), + ).Build(), + system.NewInitializeNonceAccountInstruction( + payer.PublicKey(), + nonceKey.PublicKey(), + solana.SysVarRecentBlockHashesPubkey, + solana.SysVarRentPubkey, + ).Build(), + }, + blockhash.Value.Blockhash, + solana.TransactionPayer(payer.PublicKey()), + ) + + if err != nil { + panic(err) + } + + if err := Sign(tx, nonceKey, payer); err != nil { + panic(err) + } + + ws, err := c.connectWs(ctx) + if err != nil { + return nil, fmt.Errorf("solana.connectWs() => %v", err) + } + + defer ws.Close() + + if _, err := confirm.SendAndConfirmTransaction(ctx, client, ws, tx); err != nil { + return nil, fmt.Errorf("solana.SendAndConfirmTransaction() => %v", err) + } + + return c.ReadNonceAccount(ctx, nonceKey.PublicKey()) +} + +func Sign(tx *solana.Transaction, keys ...solana.PrivateKey) error { + mapKeys := make(map[solana.PublicKey]*solana.PrivateKey) + for _, k := range keys { + mapKeys[k.PublicKey()] = &k + } + + _, err := tx.PartialSign(func(key solana.PublicKey) *solana.PrivateKey { + return mapKeys[key] + }) + return err +} + +// AddSignature add new signature to the transaction +func AddSignature(tx *solana.Transaction, pub solana.PublicKey, signature solana.Signature) error { + content, err := tx.Message.MarshalBinary() + if err != nil { + return fmt.Errorf("solana.Transaction.Message.MarshalBinary() => %v", err) + } + + if !pub.Verify(content, signature) { + return fmt.Errorf("signature verification failed") + } + + numRequiredSignatures := int(tx.Message.Header.NumRequiredSignatures) + if len(tx.Signatures) == 0 { + tx.Signatures = make([]solana.Signature, numRequiredSignatures) + } else if len(tx.Signatures) != numRequiredSignatures { + return fmt.Errorf("invalid signatures length, expected %d, actual %d", numRequiredSignatures, len(tx.Signatures)) + } + + idx := slices.Index(tx.Message.AccountKeys, pub) + if idx < 0 || idx >= numRequiredSignatures { + return fmt.Errorf("signature index out of range") + } + + tx.Signatures[idx] = signature + return nil +} + +func GetAuthorityAddressFromCreateTx(tx *solana.Transaction) solana.PublicKey { + for _, ix := range tx.Message.Instructions { + program, _ := tx.Message.Program(ix.ProgramIDIndex) + if program != squads_mpl.ProgramID { + continue + } + + accounts, err := ix.ResolveInstructionAccounts(&tx.Message) + if err != nil { + continue + } + + inst, err := squads_mpl.DecodeInstruction(accounts, ix.Data) + if err != nil { + continue + } + + create, ok := inst.Impl.(*squads_mpl.Create) + if !ok { + continue + } + + return GetDefaultAuthorityPDA(create.GetMultisigAccount().PublicKey) + } + + return solana.PublicKey{} +} + +func CheckTransactionSignedBy(tx *solana.Transaction, pub solana.PublicKey) bool { + content, err := tx.Message.MarshalBinary() + if err != nil { + panic(err) + } + + for _, sig := range tx.Signatures { + if pub.Verify(content, sig) { + return true + } + } + + return false +} + +// ValidateTransactionSignatures validates the signatures of the transaction +// 1. the length of signatures must be equal to the number of required signatures +// 2. the signature must be non-zero +func ValidateTransactionSignatures(tx *solana.Transaction) error { + if len(tx.Signatures) != int(tx.Message.Header.NumRequiredSignatures) { + return fmt.Errorf("invalid signatures length, expected %d, actual %d", tx.Message.Header.NumRequiredSignatures, len(tx.Signatures)) + } + + content, err := tx.Message.MarshalBinary() + if err != nil { + return fmt.Errorf("solana.Transaction.Message.MarshalBinary() => %v", err) + } + + for i, sig := range tx.Signatures { + if sig.IsZero() { + return fmt.Errorf("signature at index %d is zero", i) + } + + if !tx.Message.AccountKeys[i].Verify(content, sig) { + return fmt.Errorf("signature at index %d verification failed", i) + } + } + + return nil +} diff --git a/cmd/observer.go b/cmd/observer.go index 2f61dca0..2b572143 100644 --- a/cmd/observer.go +++ b/cmd/observer.go @@ -42,17 +42,20 @@ func ObserverBootCmd(c *cli.Context) error { resty.SetTimeout(time.Second * 30) resty.SetHeader("User-Agent", ua) + // mc is the configuration of observer (temporary) mc, err := config.ReadConfiguration(c.String("config"), "observer") if err != nil { return err } + // db is the SQLite3 database of observer (temporary) db, err := observer.OpenSQLite3Store(mc.Observer.StoreDir + "/safe.sqlite3") if err != nil { return err } defer db.Close() + // kd is the SQLite3 database of keeper which is read-only (temporary) kd, err := keeper.OpenSQLite3ReadOnlyStore(mc.Observer.KeeperStoreDir + "/safe.sqlite3") if err != nil { return err diff --git a/common/chain.go b/common/chain.go index 3b2798dd..a109f42a 100644 --- a/common/chain.go +++ b/common/chain.go @@ -3,6 +3,7 @@ package common import ( "github.com/MixinNetwork/safe/apps/bitcoin" "github.com/MixinNetwork/safe/apps/ethereum" + "github.com/MixinNetwork/safe/apps/solana" ) const ( @@ -10,11 +11,13 @@ const ( SafeChainLitecoin = bitcoin.ChainLitecoin SafeChainEthereum = ethereum.ChainEthereum SafeChainPolygon = ethereum.ChainPolygon + SafeChainSolana = solana.ChainSolana SafeBitcoinChainId = "c6d0c728-2624-429b-8e0d-d9d19b6592fa" SafeEthereumChainId = "43d61dcd-e413-450d-80b8-101d5e903357" SafeLitecoinChainId = "76c802a2-7c88-447f-a93e-c29c9e5dd9c8" SafePolygonChainId = "b7938396-3f94-4e0a-9179-d3440718156f" + SafeSolanaChainId = "64692c23-8971-4cf4-84a7-4dd1271dd887" ) func SafeCurveChain(crv byte) byte { @@ -27,6 +30,8 @@ func SafeCurveChain(crv byte) byte { return SafeChainEthereum case CurveSecp256k1ECDSAPolygon: return SafeChainPolygon + case CurveEdwards25519Default: + return SafeChainSolana default: panic(crv) } @@ -42,6 +47,8 @@ func SafeChainCurve(chain byte) byte { return CurveSecp256k1ECDSAEthereum case SafeChainPolygon: return CurveSecp256k1ECDSAPolygon + case SafeChainSolana: + return CurveEdwards25519Default default: panic(chain) } @@ -57,6 +64,8 @@ func SafeChainAssetId(chain byte) string { return SafeEthereumChainId case SafeChainPolygon: return SafePolygonChainId + case SafeChainSolana: + return SafeSolanaChainId default: panic(chain) } @@ -80,6 +89,8 @@ func SafeAssetIdChainNoPanic(chainId string) byte { return SafeChainEthereum case SafePolygonChainId: return SafeChainPolygon + case SafeSolanaChainId: + return SafeChainSolana } return 0 } diff --git a/common/request.go b/common/request.go index bd3fa495..cc8f7175 100644 --- a/common/request.go +++ b/common/request.go @@ -11,8 +11,10 @@ import ( "github.com/MixinNetwork/mixin/crypto" "github.com/MixinNetwork/safe/apps/bitcoin" "github.com/MixinNetwork/safe/apps/ethereum" + "github.com/MixinNetwork/safe/apps/solana" "github.com/MixinNetwork/trusted-group/mtg" "github.com/fox-one/mixin-sdk-go/v2" + sg "github.com/gagliardetto/solana-go" "github.com/gofrs/uuid/v5" "github.com/shopspring/decimal" ) @@ -65,6 +67,14 @@ const ( ActionEthereumSafeCloseAccount = 135 ActionEthereumSafeRefundTransaction = 136 + // for all Solana like chains + ActionSolanaSafeProposeAccount = 140 + ActionSolanaSafeApproveAccount = 141 + ActionSolanaSafeProposeTransaction = 142 + ActionSolanaSafeApproveTransaction = 143 + ActionSolanaSafeRevokeTransaction = 144 + ActionSolanaSafeCloseAccount = 145 + FlagProposeNormalTransaction = 0 FlagProposeRecoveryTransaction = 1 ) @@ -91,7 +101,16 @@ type AccountProposal struct { Receivers []string Threshold byte Timelock time.Duration - Observer string + Observer string // preferred observer key, optional + + // NonceAccount is the account to be used as nonce for the create safe transaction, solana only + NonceAccount sg.PublicKey + + // BlockHash is the block hash of the create safe transaction, solana only + BlockHash sg.Hash + + // PayerAccount is the account to be used as payer for the create safe transaction, solana only + PayerAccount sg.PublicKey } func (req *Request) Operation() *Operation { @@ -141,6 +160,7 @@ func (req *Request) ParseMixinRecipient(ctx context.Context, client *mixin.Clien switch req.Action { case ActionBitcoinSafeProposeAccount: case ActionEthereumSafeProposeAccount: + case ActionSolanaSafeProposeAccount: default: panic(req.Action) } @@ -151,7 +171,12 @@ func (req *Request) ParseMixinRecipient(ctx context.Context, client *mixin.Clien return nil, err } timelock := time.Duration(hours) * time.Hour - if timelock < bitcoin.TimeLockMinimum || timelock > bitcoin.TimeLockMaximum { + + if req.Action == ActionSolanaSafeProposeAccount { + if timelock != 0 { + return nil, fmt.Errorf("solana timelock must be 0") + } + } else if timelock < bitcoin.TimeLockMinimum || timelock > bitcoin.TimeLockMaximum { return nil, fmt.Errorf("timelock %d hours", hours) } @@ -179,23 +204,44 @@ func (req *Request) ParseMixinRecipient(ctx context.Context, client *mixin.Clien Receivers: receivers, Threshold: threshold, } + offset := 2 + 1 + 1 + int(total)*16 - if offset == len(extra) { - return arp, nil - } - if len(extra) != offset+33 { - return nil, fmt.Errorf("extra size %x %v", extra, arp) - } - arp.Observer = hex.EncodeToString(extra[offset:]) - switch req.Action { - case ActionBitcoinSafeProposeAccount: - err = bitcoin.VerifyHolderKey(arp.Observer) - case ActionEthereumSafeProposeAccount: - err = ethereum.VerifyHolderKey(arp.Observer) + // read nonce account & payer account for solana + if req.Action == ActionSolanaSafeProposeAccount { + if err := dec.Read(arp.NonceAccount[:]); err != nil { + return nil, err + } + + if err := dec.Read(arp.BlockHash[:]); err != nil { + return nil, err + } + + if err := dec.Read(arp.PayerAccount[:]); err != nil { + return nil, err + } + + offset += sg.PublicKeyLength * 3 } - if err != nil { - return nil, fmt.Errorf("request observer %s %v", arp.Observer, err) + + if observerBytes := extra[offset:]; len(observerBytes) > 0 { + switch req.Action { + case ActionBitcoinSafeProposeAccount: + arp.Observer = hex.EncodeToString(observerBytes) + err = bitcoin.VerifyHolderKey(arp.Observer) + case ActionEthereumSafeProposeAccount: + arp.Observer = hex.EncodeToString(observerBytes) + err = ethereum.VerifyHolderKey(arp.Observer) + case ActionSolanaSafeProposeAccount: + if len(observerBytes) != sg.PublicKeyLength { + return nil, fmt.Errorf("invalid observer length %d", len(observerBytes)) + } + + arp.Observer = sg.PublicKeyFromBytes(observerBytes).String() + } + if err != nil { + return nil, fmt.Errorf("request observer %s %v", arp.Observer, err) + } } us, err := ReadUsers(ctx, client, arp.Receivers) @@ -235,6 +281,8 @@ func (r *Request) VerifyFormat() error { return bitcoin.VerifyHolderKey(r.Holder) case CurveSecp256k1ECDSAEthereum, CurveSecp256k1ECDSAMVM, CurveSecp256k1ECDSAPolygon: return ethereum.VerifyHolderKey(r.Holder) + case CurveEdwards25519Default: + return solana.VerifyHolderKey(r.Holder) default: return fmt.Errorf("invalid request curve %v", r) } diff --git a/common/util.go b/common/util.go index 48eeeeb7..2a44ded5 100644 --- a/common/util.go +++ b/common/util.go @@ -7,6 +7,7 @@ import ( "encoding" "encoding/hex" "encoding/json" + "fmt" "os" "strings" @@ -60,8 +61,8 @@ func CheckTestEnvironment(ctx context.Context) bool { return mtg.CheckTestEnvironment(ctx) } -func CheckUnique(args ...any) bool { - filter := make(map[any]struct{}) +func CheckUnique[T comparable](args ...T) bool { + filter := make(map[T]struct{}, len(args)) for _, k := range args { filter[k] = struct{}{} } @@ -98,3 +99,15 @@ func CheckTransactionRetryError(err string) bool { } return false } + +func Must[T any](v T, err error) T { + if err != nil { + panic(fmt.Errorf("must: %w", err)) + } + + return v +} + +func Try[T any](v T, err error) T { + return v +} diff --git a/config/example.toml b/config/example.toml index c008bf39..340782b3 100644 --- a/config/example.toml +++ b/config/example.toml @@ -79,6 +79,8 @@ bitcoin-rpc = "https://mixin:safe@bitcoin.mixin.dev" litecoin-rpc = "https://mixin:safe@litecoin.mixin.dev" ethereum-rpc = "https://cloudflare-eth.com" polygon-rpc = "https://polygon-bor.publicnode.com" +solana-rpc = "https://api.devnet.solana.com" +solana-ws-rpc = "wss://api.devnet.solana.com" polygon-factory-address = "0x4D17777E0AC12C6a0d4DEF1204278cFEAe142a1E" polygon-observer-deposit-entry = "0x4A2eea63775F0407E1f0d147571a46959479dE12" polygon-keeper-deposit-entry = "0x5A3A6E35038f33458c13F3b5349ee5Ae1e94a8d9" diff --git a/go.mod b/go.mod index cf3a808c..a952bfdc 100644 --- a/go.mod +++ b/go.mod @@ -28,14 +28,19 @@ require ( ) require ( + contrib.go.opencensus.io/exporter/stackdriver v0.13.4 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/MixinNetwork/go-number v0.1.1 // indirect github.com/StackExchange/wmi v1.2.1 // indirect github.com/aead/siphash v1.0.1 // indirect + github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect + github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect github.com/bits-and-blooms/bitset v1.15.0 // indirect + github.com/blendle/zapdriver v1.3.1 // indirect github.com/btcsuite/btclog v0.0.0-20241017175713-3428138b75c7 // indirect github.com/btcsuite/btcutil v1.0.2 // indirect + github.com/buger/jsonparser v1.1.1 // indirect github.com/consensys/bavard v0.1.22 // indirect github.com/consensys/gnark-crypto v0.14.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect @@ -45,30 +50,53 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect + github.com/dfuse-io/logging v0.0.0-20210109005628-b97a57253f70 // indirect github.com/ethereum/c-kzg-4844 v1.0.0 // indirect github.com/ethereum/c-kzg-4844/bindings/go v0.0.0-20230126171313-363c7d7593b4 // indirect github.com/ethereum/go-verkle v0.2.2 // indirect + github.com/fatih/color v1.16.0 // indirect github.com/fox-one/msgpack v1.0.0 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/gagliardetto/binary v0.8.0 // indirect + github.com/gagliardetto/gofuzz v1.2.2 // indirect + github.com/gagliardetto/solana-go v1.12.0 // indirect + github.com/gagliardetto/treeout v0.1.4 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-resty/resty/v2 v2.16.0 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/rpc v1.2.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/holiman/uint256 v1.3.1 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/kkdai/bstream v1.0.0 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/logrusorgru/aurora v2.0.3+incompatible // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect + github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/supranational/blst v0.3.13 // indirect + github.com/teris-io/shortid v0.0.0-20201117134242-e59966efd125 // indirect + github.com/tidwall/gjson v1.9.3 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/numcpus v0.9.0 // indirect github.com/vmihailenco/tagparser v0.1.2 // indirect @@ -76,10 +104,18 @@ require ( github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zeebo/blake3 v0.2.4 // indirect + go.mongodb.org/mongo-driver v1.12.2 // indirect + go.opencensus.io v0.24.0 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/ratelimit v0.2.0 // indirect + go.uber.org/zap v1.21.0 // indirect golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect golang.org/x/net v0.31.0 // indirect golang.org/x/sync v0.9.0 // indirect golang.org/x/sys v0.27.0 // indirect + golang.org/x/term v0.26.0 // indirect + golang.org/x/time v0.6.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.35.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index d7864cc8..eb3d45ff 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,39 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +contrib.go.opencensus.io/exporter/stackdriver v0.12.6/go.mod h1:8x999/OcIPy5ivx/wDiV7Gx4D+VUPODf0mWRGRc5kSk= +contrib.go.opencensus.io/exporter/stackdriver v0.13.4 h1:ksUxwH3OD5sxkjzEqGxNTl+Xjsmu3BnC/300MhSVTSc= +contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= +github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/MixinNetwork/bot-api-go-client/v3 v3.9.2 h1:X7IJnVxILXu0FWqO5kZNJVXDUBf9jxz3qr1Xw5lGt3A= @@ -14,12 +46,32 @@ github.com/MixinNetwork/multi-party-sig v0.4.1 h1:rQdIVSDQQOUMub8ERDV1gbFHxGSD5/ github.com/MixinNetwork/multi-party-sig v0.4.1/go.mod h1:mnZyPutnRV2+E6z3v5TpTb7q4HnS7IplS0yy4dPjVGA= github.com/MixinNetwork/trusted-group v0.9.6 h1:lCDPTRm0e2CCsn6Ud2FdLK2HYUp0nZOU0QI1Jj8Qj7s= github.com/MixinNetwork/trusted-group v0.9.6/go.mod h1:69uIhwprk2hob3K6tvDfBEOYjYXyLKDtlx5UZDhgzvo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= +github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go v1.22.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 h1:WWB576BN5zNSZc/M9d/10pqEx5VHNhaQ/yOVAkmj5Yo= +github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bits-and-blooms/bitset v1.15.0 h1:DiCRMscZsGyYePE9AR3sVhKqUXCt5IZvkX5AfAc5xLQ= github.com/bits-and-blooms/bitset v1.15.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE= +github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= @@ -53,12 +105,25 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/consensys/bavard v0.1.22 h1:Uw2CGvbXSZWhqK59X0VG/zOjpTFuOMcPLStrp1ihI0A= github.com/consensys/bavard v0.1.22/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= github.com/consensys/gnark-crypto v0.14.0 h1:DDBdl4HaBtdQsq/wfMwJvZNE80sHidrK3Nfrefatm0E= github.com/consensys/gnark-crypto v0.14.0/go.mod h1:CU4UijNPsHawiVGNxe9co07FkzCeWHHrb1li/n1XoU0= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= @@ -68,6 +133,7 @@ github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlX github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cronokirby/saferith v0.33.0 h1:TgoQlfsD4LIwx71+ChfRcIpjkw+RPOapDEVxa+LhwLo= github.com/cronokirby/saferith v0.33.0/go.mod h1:QKJhjoqUtBsXCAVEjw38mFqoi7DebT7kthcD7UzbnoA= +github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -81,9 +147,16 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeC github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/dfuse-io/logging v0.0.0-20201110202154-26697de88c79/go.mod h1:V+ED4kT/t/lKtH99JQmKIb0v9WL3VaYkJ36CfHlVECI= +github.com/dfuse-io/logging v0.0.0-20210109005628-b97a57253f70 h1:CuJS05R9jmNlUK8GOxrEELPbfXm0EuGh/30LjkjN5vo= +github.com/dfuse-io/logging v0.0.0-20210109005628-b97a57253f70/go.mod h1:EoK/8RFbMEteaCaz89uessDTnCWjbbcr+DXcBh4el5o= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dimfeld/httptreemux/v5 v5.5.0 h1:p8jkiMrCuZ0CmhwYLcbNbl7DDo21fozhKHQ2PccwOFQ= github.com/dimfeld/httptreemux/v5 v5.5.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= @@ -93,6 +166,10 @@ github.com/ethereum/go-ethereum v1.14.11 h1:8nFDCUUE67rPc6AKxFj7JKaOa2W/W1Rse3oS github.com/ethereum/go-ethereum v1.14.11/go.mod h1:+l/fr42Mma+xBnhefL/+z11/hcmJ2egl+ScIVPjhc7E= github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fox-one/mixin-sdk-go/v2 v2.0.10 h1:U0aOCCsZOM3xSGYnZWcEanDPp28EoZLIC7CE8uY1idQ= github.com/fox-one/mixin-sdk-go/v2 v2.0.10/go.mod h1:3oaTbgw3ERL7UVi5E40NenQ16EkBVV7X++brLM1uWqU= github.com/fox-one/msgpack v1.0.0 h1:atr4La29WdMPCoddlRAPK2e1yhBJ2cEFF+2X93KY5Vs= @@ -103,6 +180,25 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/ github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/gagliardetto/binary v0.6.1 h1:vGrbUym10xaaswadfnuSDr0xlP3NZS5XWbLqENJidrI= +github.com/gagliardetto/binary v0.6.1/go.mod h1:aOfYkc20U0deHaHn/LVZXiqlkDbFAX0FpTlDhsXa0S0= +github.com/gagliardetto/binary v0.8.0 h1:U9ahc45v9HW0d15LoN++vIXSJyqR/pWw8DDlhd7zvxg= +github.com/gagliardetto/binary v0.8.0/go.mod h1:2tfj51g5o9dnvsc+fL3Jxr22MuWzYXwx9wEoN0XQ7/c= +github.com/gagliardetto/gofuzz v1.2.2 h1:XL/8qDMzcgvR4+CyRQW9UGdwPRPMHVJfqQ/uMvSUuQw= +github.com/gagliardetto/gofuzz v1.2.2/go.mod h1:bkH/3hYLZrMLbfYWA0pWzXmi5TTRZnu4pMGZBkqMKvY= +github.com/gagliardetto/solana-go v1.4.0 h1:B6O4F7ZyOHLmZTF0jvJQZohriVwo15vo5XKJQWMbbWM= +github.com/gagliardetto/solana-go v1.4.0/go.mod h1:NFuoDwHPvw858ZMHUJr6bkhN8qHt4x6e+U3EYHxAwNY= +github.com/gagliardetto/solana-go v1.12.0 h1:rzsbilDPj6p+/DOPXBMLhwMZeBgeRuXjm5zQFCoXgsg= +github.com/gagliardetto/solana-go v1.12.0/go.mod h1:l/qqqIN6qJJPtxW/G1PF4JtcE3Zg2vD2EliZrr9Gn5k= +github.com/gagliardetto/treeout v0.1.4 h1:ozeYerrLCmCubo1TcIjFiOWTTGteOOHND1twdFpgwaw= +github.com/gagliardetto/treeout v0.1.4/go.mod h1:loUefvXTrlRG5rYmJmExNryyBRh8f89VZhmMOyCyqok= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= @@ -110,19 +206,33 @@ github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW github.com/go-resty/resty/v2 v2.12.0/go.mod h1:o0yGPrkS3lOe1+eFajk6kBW8ScXzwU3hD69/gt2yB/0= github.com/go-resty/resty/v2 v2.16.0 h1:qpKalHWI2bpp9BIKlyT8TYWEJXOk1NuKbfiT3RRnzWc= github.com/go-resty/resty/v2 v2.16.0/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk= github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -135,46 +245,150 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk= +github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/holiman/uint256 v1.3.1 h1:JfTzmih28bittyHM8z360dCjIA9dbPIBlcTI6lmctQs= github.com/holiman/uint256 v1.3.1/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8= github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA= +github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= +github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mdp/qrterminal v1.0.1 h1:07+fzVDlPuBlXS8tB0ktTAyf+Lp1j2+2zK3fBOL5b7c= github.com/mdp/qrterminal v1.0.1/go.mod h1:Z33WhxQe9B6CdW37HaVqcRKzP+kByF3q/qLxOGe12xQ= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 h1:mPMvm6X6tf4w8y7j9YIt6V9jfWhL6QlbEc7CCmeQlWk= +github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1/go.mod h1:ye2e/VUEtE2BHE+G/QcKkcLQVAEJoYRFj5VUOQatCRE= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= @@ -185,47 +399,109 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 h1:RN5mrigyirb8anBEtdjtHFIufXdacyTi6i4KBfeNXeo= +github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091/go.mod h1:VlduQ80JcGJSargkRU4Sg9Xo63wZD/l8A5NC/Uo1/uU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/supranational/blst v0.3.13 h1:AYeSxdOMacwu7FBmpfloBz5pbFXDmJL33RuwnKtmTjk= github.com/supranational/blst v0.3.13/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= +github.com/teris-io/shortid v0.0.0-20201117134242-e59966efd125 h1:3SNcvBmEPE1YlB1JpVZouslJpI3GBNoiqW7+wb0Rz7w= +github.com/teris-io/shortid v0.0.0-20201117134242-e59966efd125/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= +github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= +github.com/tidwall/gjson v1.9.3 h1:hqzS9wAHMO+KVBBkLxYdkEeeFHuqr95GfClRLKlgK0E= +github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= @@ -234,37 +510,122 @@ github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvv github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.mongodb.org/mongo-driver v1.12.2 h1:gbWY1bJkkmUB9jjZzcdhOL8O85N9H+Vvsf2yFN0RDws= +go.mongodb.org/mongo-driver v1.12.2/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA= +go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.14.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= @@ -274,31 +635,70 @@ golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -306,44 +706,131 @@ golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200601175630-2caf76543d99/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -358,19 +845,36 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +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= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/keeper/common.go b/keeper/common.go index 55e6db24..81ab23a7 100644 --- a/keeper/common.go +++ b/keeper/common.go @@ -31,6 +31,10 @@ func ethereumDefaultDerivationPath() []byte { return []byte{0, 0, 0, 0} } +func solanaDefaultDerivationPath() string { + return "m/44'/501'/0'/0'" +} + func (node *Node) failRequest(ctx context.Context, req *common.Request, assetId string) ([]*mtg.Transaction, string) { logger.Printf("node.failRequest(%v, %s)", req, assetId) err := node.store.FailRequest(ctx, req, assetId, nil) diff --git a/keeper/deposit.go b/keeper/deposit.go index b1165507..ea6f177f 100644 --- a/keeper/deposit.go +++ b/keeper/deposit.go @@ -13,10 +13,12 @@ import ( "github.com/MixinNetwork/mixin/logger" "github.com/MixinNetwork/safe/apps/bitcoin" "github.com/MixinNetwork/safe/apps/ethereum" + "github.com/MixinNetwork/safe/apps/solana" "github.com/MixinNetwork/safe/common" "github.com/MixinNetwork/safe/keeper/store" "github.com/MixinNetwork/trusted-group/mtg" gc "github.com/ethereum/go-ethereum/common" + sg "github.com/gagliardetto/solana-go" "github.com/gofrs/uuid/v5" "github.com/shopspring/decimal" ) @@ -56,6 +58,13 @@ func parseDepositExtra(req *common.Request) (*Deposit, error) { deposit.AssetAddress = gc.BytesToAddress(extra[32:52]).Hex() deposit.Index = binary.BigEndian.Uint64(extra[52:60]) deposit.Amount = new(big.Int).SetBytes(extra[60:]) + case common.SafeChainSolana: + // use transaction signature as hash + deposit.Hash = sg.SignatureFromBytes(extra[0:64]).String() + // use mint address as asset address + deposit.AssetAddress = sg.PublicKeyFromBytes(extra[64:96]).String() + deposit.Index = binary.BigEndian.Uint64(extra[96:104]) + deposit.Amount = new(big.Int).SetBytes(extra[104:]) default: return nil, fmt.Errorf("invalid deposit chain %d", deposit.Chain) } @@ -98,7 +107,7 @@ func (node *Node) CreateHolderDeposit(ctx context.Context, req *common.Request) if bond.Decimals != 18 || bond.AssetId != safeAssetId || bond.Chain != common.SafeChainPolygon { panic(bond.AssetId) } - asset, err := node.fetchAssetMetaFromMessengerOrEthereum(ctx, deposit.Asset, deposit.AssetAddress, deposit.Chain) + asset, err := node.fetchAssetMetaFromMessengerOrNetwork(ctx, deposit.Asset, deposit.AssetAddress, deposit.Chain) if err != nil { panic(fmt.Errorf("node.fetchAssetMeta(%s) => %v", deposit.Asset, err)) } @@ -119,6 +128,8 @@ func (node *Node) CreateHolderDeposit(ctx context.Context, req *common.Request) return node.doBitcoinHolderDeposit(ctx, req, deposit, safe, bond.AssetId, asset, plan.TransactionMinimum) case common.SafeChainEthereum, common.SafeChainPolygon: return node.doEthereumHolderDeposit(ctx, req, deposit, safe, bond.AssetId, asset) + case common.SafeChainSolana: + return node.doSolanaHolderDeposit(ctx, req, deposit, safe, safeAssetId, asset) default: return node.failRequest(ctx, req, "") } @@ -251,6 +262,97 @@ func (node *Node) doEthereumHolderDeposit(ctx context.Context, req *common.Reque return []*mtg.Transaction{t}, "" } +func (node *Node) doSolanaHolderDeposit(ctx context.Context, req *common.Request, deposit *Deposit, safe *store.Safe, safeAssetId string, asset *store.Asset) ([]*mtg.Transaction, string) { + if asset.AssetId == common.SafeSolanaChainId && asset.Decimals != solana.NativeTokenDecimals { + panic(asset.Decimals) + } + + // 检查是否已经处理过这笔充值 + deposited, err := node.store.ReadDeposit(ctx, deposit.Hash, int64(deposit.Index)) + logger.Printf("store.ReadDeposit(%s, %d, %s, %s) => %v %v", deposit.Hash, int64(deposit.Index), asset.AssetId, safe.Address, deposited, err) + if err != nil { + panic(fmt.Errorf("store.ReadDeposit(%s, %d, %s, %s) => %v", deposit.Hash, int64(deposit.Index), asset.AssetId, safe.Address, err)) + } else if deposited != nil { + return node.failRequest(ctx, req, "") + } + + // 读取并更新 Safe 余额 + safeBalance, err := node.store.ReadSolanaBalance(ctx, safe.Address, asset.AssetId, safeAssetId) + logger.Printf("store.ReadSolanaBalance(%s, %s) => %v %v", safe.Address, asset.AssetId, safeBalance, err) + if err != nil { + panic(err) + } + + safeBalance.UpdateBalance(deposit.Amount) + if safeBalance.AssetAddress == "" { + safeBalance.AssetAddress = deposit.AssetAddress + } + + // 验证 Solana 交易 + output, err := node.verifySolanaTransaction(ctx, req, deposit, safe) + logger.Printf("node.verifySolanaTransaction(%v) => %v %v", req, output, err) + if err != nil { + panic(fmt.Errorf("node.verifySolanaTransaction(%s) => %v", deposit.Hash, err)) + } + if output == nil { + return node.failRequest(ctx, req, "") + } + + // 构建交易 + t := node.buildTransaction(ctx, req.Output, safe.RequestId, safeAssetId, safe.Receivers, int(safe.Threshold), decimal.NewFromBigInt(deposit.Amount, -int32(asset.Decimals)).String(), nil, req.Id) + if t == nil { + return node.failRequest(ctx, req, "") + } + + // 保存充值记录 + err = node.store.CreateSolanaBalanceDepositFromRequest(ctx, safe, safeBalance, deposit.Hash, int64(deposit.Index), deposit.Amount, output.Sender, req, []*mtg.Transaction{t}) + logger.Printf("store.CreateSolanaBalanceDepositFromRequest(%v) => %v", req, err) + if err != nil { + panic(err) + } + + return []*mtg.Transaction{t}, "" +} + +func (node *Node) verifySolanaTransaction(ctx context.Context, req *common.Request, deposit *Deposit, safe *store.Safe) (*solana.Transfer, error) { + info, err := node.store.ReadLatestNetworkInfo(ctx, safe.Chain, req.CreatedAt) + logger.Printf("store.ReadLatestNetworkInfo(%d) => %v %v", safe.Chain, info, err) + if err != nil || info == nil { + return nil, err + } + if info.CreatedAt.After(req.CreatedAt) { + return nil, fmt.Errorf("malicious solana network info %v", info) + } + + // 验证交易 + client := node.solanaClient() + t, stx, err := client.VerifyDeposit(ctx, deposit.Hash, deposit.AssetAddress, safe.Address, int64(deposit.Index), deposit.Amount) + if err != nil || t == nil { + return nil, fmt.Errorf("malicious solana deposit or node not in sync? %s %v", deposit.Hash, err) + } + if t.Receiver != safe.Address { + return nil, fmt.Errorf("malicious solana deposit %s", deposit.Hash) + } + + // 检查确认数 + confirmations := info.Height - stx.Slot + 1 + if info.Height < stx.Slot { + confirmations = 0 + } + isSafe, err := node.checkTrustedSender(ctx, t.Sender) + if err != nil { + return nil, fmt.Errorf("node.checkTrustedSender(%s) => %v", t.Sender, err) + } + if isSafe && confirmations > 0 { + confirmations = 1000000 + } + if !solana.CheckFinalization(confirmations) { + return nil, fmt.Errorf("solana.CheckFinalization(%s)", deposit.Hash) + } + + return t, nil +} + func (node *Node) checkBitcoinChange(ctx context.Context, deposit *Deposit, btx *bitcoin.RPCTransaction) (bool, error) { vin, spentBy, err := node.store.ReadBitcoinUTXO(ctx, btx.Vin[0].TxId, int(btx.Vin[0].VOUT)) if err != nil || vin == nil { diff --git a/keeper/ethereum.go b/keeper/ethereum.go index de886bff..aa0073fc 100644 --- a/keeper/ethereum.go +++ b/keeper/ethereum.go @@ -670,7 +670,7 @@ func (node *Node) processEthereumSafeProposeTransaction(ctx context.Context, req return node.failRequest(ctx, req, "") } balances, err := node.store.ReadAllEthereumTokenBalances(ctx, safe.Address) - logger.Printf("store.ReadAllEthereumTokenBalances(%s) => %v %v", safe.Address, balances, err) + logger.Printf("store.ReadAllEthereumTokenBalances(%s) => %v , m%v", safe.Address, balances, err) if err != nil { panic(err) } diff --git a/keeper/group.go b/keeper/group.go index a077008b..b6a75068 100644 --- a/keeper/group.go +++ b/keeper/group.go @@ -9,6 +9,7 @@ import ( "github.com/MixinNetwork/mixin/logger" "github.com/MixinNetwork/safe/apps/bitcoin" "github.com/MixinNetwork/safe/apps/ethereum" + "github.com/MixinNetwork/safe/apps/solana" "github.com/MixinNetwork/safe/common" "github.com/MixinNetwork/safe/common/abi" "github.com/MixinNetwork/trusted-group/mtg" @@ -61,6 +62,7 @@ func (node *Node) processAction(ctx context.Context, out *mtg.Action) ([]*mtg.Tr role := node.getActionRole(req.Action) if role == 0 || role != req.Role { + logger.Printf("node.getActionRole(%v,%v) => %v %v", req.Id,req.Action, role, req.Role) return nil, "" } @@ -98,17 +100,17 @@ func (node *Node) getActionRole(act byte) byte { return common.RequestRoleObserver case common.ActionMigrateSafeToken: return common.RequestRoleHolder - case common.ActionBitcoinSafeProposeAccount, common.ActionEthereumSafeProposeAccount: + case common.ActionBitcoinSafeProposeAccount, common.ActionEthereumSafeProposeAccount, common.ActionSolanaSafeProposeAccount: return common.RequestRoleHolder - case common.ActionBitcoinSafeApproveAccount, common.ActionEthereumSafeApproveAccount: + case common.ActionBitcoinSafeApproveAccount, common.ActionEthereumSafeApproveAccount, common.ActionSolanaSafeApproveAccount: return common.RequestRoleObserver - case common.ActionBitcoinSafeProposeTransaction, common.ActionEthereumSafeProposeTransaction: + case common.ActionBitcoinSafeProposeTransaction, common.ActionEthereumSafeProposeTransaction, common.ActionSolanaSafeProposeTransaction: return common.RequestRoleHolder - case common.ActionBitcoinSafeApproveTransaction, common.ActionEthereumSafeApproveTransaction: + case common.ActionBitcoinSafeApproveTransaction, common.ActionEthereumSafeApproveTransaction, common.ActionSolanaSafeApproveTransaction: return common.RequestRoleObserver - case common.ActionBitcoinSafeRevokeTransaction, common.ActionEthereumSafeRevokeTransaction: + case common.ActionBitcoinSafeRevokeTransaction, common.ActionEthereumSafeRevokeTransaction, common.ActionSolanaSafeRevokeTransaction: return common.RequestRoleObserver - case common.ActionBitcoinSafeCloseAccount, common.ActionEthereumSafeCloseAccount: + case common.ActionBitcoinSafeCloseAccount, common.ActionEthereumSafeCloseAccount, common.ActionSolanaSafeCloseAccount: return common.RequestRoleObserver case common.ActionEthereumSafeRefundTransaction: return common.RequestRoleObserver @@ -208,6 +210,18 @@ func (node *Node) processRequest(ctx context.Context, req *common.Request) ([]*m return node.processEthereumSafeCloseAccount(ctx, req) case common.ActionEthereumSafeRefundTransaction: return node.processEthereumSafeRefundTransaction(ctx, req) + case common.ActionSolanaSafeProposeAccount: + return node.processSolanaSafeProposeAccount(ctx, req) + case common.ActionSolanaSafeApproveAccount: + return node.processSolanaSafeApproveAccount(ctx, req) + case common.ActionSolanaSafeProposeTransaction: + return node.processSolanaSafeProposeTransaction(ctx, req) + case common.ActionSolanaSafeApproveTransaction: + return node.processSolanaSafeApproveTransaction(ctx, req) + case common.ActionSolanaSafeRevokeTransaction: + return node.processSafeRevokeTransaction(ctx, req) + case common.ActionSolanaSafeCloseAccount: + return node.processSolanaSafeCloseAccount(ctx, req) default: panic(req.Action) } @@ -258,6 +272,12 @@ func (node *Node) processKeyAdd(ctx context.Context, req *common.Request) ([]*mt if err != nil { return node.failRequest(ctx, req, "") } + case common.CurveEdwards25519Default: + err = solana.VerifyHolderKey(req.Holder) + logger.Printf("solana.VerifyHolderKey(%s, %x) => %v", req.Holder, chainCode, err) + if err != nil { + return node.failRequest(ctx, req, "") + } default: panic(req.Curve) } @@ -296,6 +316,8 @@ func (node *Node) processSignerSignatureResponse(ctx context.Context, req *commo return node.processBitcoinSafeSignatureResponse(ctx, req, safe, tx, old) case common.SafeChainEthereum, common.SafeChainPolygon: return node.processEthereumSafeSignatureResponse(ctx, req, safe, tx, old) + case common.SafeChainSolana: + return node.processSolanaSafeSignatureResponse(ctx, req, safe, tx, old) default: panic(safe.Chain) } diff --git a/keeper/interface.go b/keeper/interface.go index 2be389ca..33836b59 100644 --- a/keeper/interface.go +++ b/keeper/interface.go @@ -22,6 +22,8 @@ type Configuration struct { LitecoinRPC string `toml:"litecoin-rpc"` EthereumRPC string `toml:"ethereum-rpc"` PolygonRPC string `toml:"polygon-rpc"` + SolanaRPC string `toml:"solana-rpc"` + SolanaWsRPC string `toml:"solana-ws-rpc"` PolygonFactoryAddress string `toml:"polygon-factory-address"` PolygonObserverDepositEntry string `toml:"polygon-observer-deposit-entry"` PolygonKeeperDepositEntry string `toml:"polygon-keeper-deposit-entry"` diff --git a/keeper/keeper_test.go b/keeper/keeper_test.go index f0ce0187..3e19386d 100644 --- a/keeper/keeper_test.go +++ b/keeper/keeper_test.go @@ -879,7 +879,7 @@ func testReadObserverResponse(ctx context.Context, require *require.Assertions, v, _ := node.store.ReadProperty(ctx, id) var om map[string]any err := json.Unmarshal([]byte(v), &om) - require.Nil(err) + require.Nil(err, "id: %q, val: %q", id, v) require.Equal(node.conf.ObserverUserId, om["receivers"].([]any)[0]) switch typ { case common.ActionBitcoinSafeApproveAccount: @@ -890,6 +890,10 @@ func testReadObserverResponse(ctx context.Context, require *require.Assertions, params, _ := node.store.ReadLatestOperationParams(ctx, common.SafeChainPolygon, time.Now()) require.Equal(params.OperationPriceAsset, om["asset_id"]) require.Equal(params.OperationPriceAmount.String(), om["amount"]) + case common.ActionSolanaSafeApproveAccount: + params, _ := node.store.ReadLatestOperationParams(ctx, common.SafeChainSolana, time.Now()) + require.Equal(params.OperationPriceAsset, om["asset_id"]) + require.Equal(params.OperationPriceAmount.String(), om["amount"]) default: require.Equal(node.conf.ObserverAssetId, om["asset_id"]) require.Equal("1", om["amount"]) @@ -913,6 +917,8 @@ func testBuildHolderRequest(node *Node, id, public string, action byte, assetId case common.ActionBitcoinSafeProposeAccount, common.ActionBitcoinSafeProposeTransaction: case common.ActionEthereumSafeProposeAccount, common.ActionEthereumSafeProposeTransaction: crv = common.CurveSecp256k1ECDSAPolygon + case common.ActionSolanaSafeProposeAccount, common.ActionSolanaSafeProposeTransaction: + crv = common.CurveEdwards25519Default } op := &common.Operation{ Id: id, @@ -974,6 +980,7 @@ func testBuildSignerOutput(node *Node, id, public string, action byte, extra []b path := bitcoinDefaultDerivationPath() switch crv { case common.CurveSecp256k1ECDSABitcoin: + case common.CurveEdwards25519Default: case common.CurveSecp256k1ECDSAEthereum, common.CurveSecp256k1ECDSAPolygon: path = ethereumDefaultDerivationPath() default: diff --git a/keeper/migrate.go b/keeper/migrate.go index 5b0ace5e..e37a508c 100644 --- a/keeper/migrate.go +++ b/keeper/migrate.go @@ -145,6 +145,18 @@ func (node *Node) checkSafeTokenMigration(ctx context.Context, req *common.Reque if err != nil { panic(err) } + found := slices.IndexFunc(bs, func(sb *store.SafeBalance) bool { + return sb.SafeAssetId == req.AssetId + }) + if found < 0 && req.AssetId != safe.SafeAssetId { + panic(req.AssetId) + } + case common.SafeChainSolana: + bs, err := node.store.ReadAllSolanaTokenBalances(ctx, safe.Address) + if err != nil { + panic(err) + } + found := slices.IndexFunc(bs, func(sb *store.SafeBalance) bool { return sb.SafeAssetId == req.AssetId }) diff --git a/keeper/network.go b/keeper/network.go index 95756516..ed5773c5 100644 --- a/keeper/network.go +++ b/keeper/network.go @@ -15,6 +15,7 @@ import ( "github.com/MixinNetwork/mixin/logger" "github.com/MixinNetwork/safe/apps/bitcoin" "github.com/MixinNetwork/safe/apps/ethereum" + "github.com/MixinNetwork/safe/apps/solana" "github.com/MixinNetwork/safe/common" "github.com/MixinNetwork/safe/keeper/store" "github.com/MixinNetwork/trusted-group/mtg" @@ -77,6 +78,15 @@ func (node *Node) writeNetworkInfo(ctx context.Context, req *common.Request) ([] } else if !valid { return node.failRequest(ctx, req, "") } + case common.SafeChainSolana: + info.Hash = hex.EncodeToString(extra[17:]) + valid, err := node.verifySolanaNetworkInfo(info, old) + logger.Printf("node.verifySolanaNetworkInfo(%s, %v) => %t", req.Id, info, valid) + if err != nil { + panic(fmt.Errorf("node.verifySolanaNetworkInfo(%v) => %v", info, err)) + } else if !valid { + return node.failRequest(ctx, req, "") + } default: return node.failRequest(ctx, req, "") } @@ -106,6 +116,7 @@ func (node *Node) writeOperationParams(ctx context.Context, req *common.Request) case common.SafeChainLitecoin: case common.SafeChainEthereum: case common.SafeChainPolygon: + case common.SafeChainSolana: default: return node.failRequest(ctx, req, "") } @@ -163,7 +174,7 @@ func (node *Node) verifyEthereumNetworkInfo(info, old *store.NetworkInfo) (bool, } if old != nil && old.Hash == info.Hash { if old.Height != info.Height { - return false, fmt.Errorf("malicious bitcoin block %s", info.Hash) + return false, fmt.Errorf("malicious ethereum block %s", info.Hash) } } else { rpc, _ := node.ethereumParams(info.Chain) @@ -178,6 +189,31 @@ func (node *Node) verifyEthereumNetworkInfo(info, old *store.NetworkInfo) (bool, return true, nil } +// todo: implement solana network info verification +func (node *Node) verifySolanaNetworkInfo(info, old *store.NetworkInfo) (bool, error) { + if len(info.Hash) != 64 { + return false, nil + } + + if old != nil && old.Hash == info.Hash { + if old.Height != info.Height { + return false, fmt.Errorf("malicious solana block %s", info.Hash) + } + } else { + // info.Height is the slot number + client := node.solanaClient() + block, err := client.RPCGetBlock(context.TODO(), info.Height) + if err != nil || block == nil { + return false, fmt.Errorf("malicious solana block or node not in sync? %s %v", info.Hash, err) + } + + if hex.EncodeToString(block.Blockhash[:]) != info.Hash { + return false, fmt.Errorf("malicious solana block %s, %s", info.Hash, hex.EncodeToString(block.Blockhash[:])) + } + } + return true, nil +} + func (node *Node) bitcoinParams(chain byte) (string, string) { switch chain { case common.SafeChainBitcoin: @@ -200,33 +236,56 @@ func (node *Node) ethereumParams(chain byte) (string, string) { } } -func (node *Node) fetchAssetMetaFromMessengerOrEthereum(ctx context.Context, id, assetContract string, chain byte) (*store.Asset, error) { +func (node *Node) solanaClient() *solana.Client { + return solana.NewClient(node.conf.SolanaRPC, node.conf.SolanaWsRPC) +} + +func (node *Node) fetchAssetMetaFromMessengerOrNetwork(ctx context.Context, id, assetContract string, chain byte) (*store.Asset, error) { meta, err := node.fetchAssetMeta(ctx, id) if err != nil || meta != nil { return meta, err } switch chain { - case common.SafeChainEthereum: - case common.SafeChainPolygon: + case common.SafeChainEthereum, common.SafeChainPolygon: + rpc, _ := node.ethereumParams(chain) + token, err := ethereum.FetchAsset(chain, rpc, assetContract) + if err != nil { + return nil, err + } + asset := &store.Asset{ + AssetId: token.Id, + MixinId: crypto.Sha256Hash([]byte(token.Id)).String(), + AssetKey: token.Address, + Symbol: token.Symbol, + Name: token.Name, + Decimals: token.Decimals, + Chain: token.Chain, + CreatedAt: time.Now().UTC(), + } + return asset, node.store.WriteAssetMeta(ctx, asset) + case common.SafeChainSolana: + client := node.solanaClient() + token, err := client.RPCGetAsset(ctx, assetContract) + if err != nil { + return nil, err + } + + asset := &store.Asset{ + AssetId: token.Id, + MixinId: crypto.Sha256Hash([]byte(token.Id)).String(), + AssetKey: token.Address, + Symbol: token.Symbol, + Name: token.Name, + Decimals: token.Decimals, + Chain: chain, + CreatedAt: time.Now().UTC(), + } + + return asset, node.store.WriteAssetMeta(ctx, asset) default: panic(chain) } - rpc, _ := node.ethereumParams(chain) - token, err := ethereum.FetchAsset(chain, rpc, assetContract) - if err != nil { - return nil, err - } - asset := &store.Asset{ - AssetId: token.Id, - MixinId: crypto.Sha256Hash([]byte(token.Id)).String(), - AssetKey: token.Address, - Symbol: token.Symbol, - Name: token.Name, - Decimals: token.Decimals, - Chain: token.Chain, - CreatedAt: time.Now().UTC(), - } - return asset, node.store.WriteAssetMeta(ctx, asset) + } func (node *Node) fetchMixinAsset(_ context.Context, id string) (*store.Asset, error) { diff --git a/keeper/signer.go b/keeper/signer.go index 560d1d74..9b16b885 100644 --- a/keeper/signer.go +++ b/keeper/signer.go @@ -26,6 +26,7 @@ func (node *Node) processSignerKeygenRequests(ctx context.Context, req *common.R switch crv { case common.CurveSecp256k1ECDSABitcoin: case common.CurveSecp256k1ECDSAEthereum: + case common.CurveEdwards25519Default: default: return node.failRequest(ctx, req, "") } diff --git a/keeper/solana.go b/keeper/solana.go new file mode 100644 index 00000000..962b78a3 --- /dev/null +++ b/keeper/solana.go @@ -0,0 +1,894 @@ +package keeper + +import ( + "bytes" + "context" + "encoding/hex" + "encoding/json" + "fmt" + "math/big" + + "github.com/MixinNetwork/mixin/crypto" + "github.com/MixinNetwork/mixin/logger" + "github.com/MixinNetwork/safe/apps/ethereum" + "github.com/MixinNetwork/safe/apps/solana" + "github.com/MixinNetwork/safe/common" + "github.com/MixinNetwork/safe/common/abi" + "github.com/MixinNetwork/safe/keeper/store" + "github.com/MixinNetwork/trusted-group/mtg" + sg "github.com/gagliardetto/solana-go" + "github.com/gofrs/uuid/v5" + "github.com/shopspring/decimal" +) + +func (node *Node) processSolanaSafeCloseAccount(ctx context.Context, req *common.Request) ([]*mtg.Transaction, string) { + if req.Role != common.RequestRoleObserver { + panic(req.Role) + } + + safe, err := node.store.ReadSafe(ctx, req.Holder) + if err != nil { + panic(fmt.Errorf("store.ReadSafe(%s) => %v", req.Holder, err)) + } + + if safe == nil || safe.Chain != common.SafeChainSolana { + return node.failRequest(ctx, req, "") + } + + switch safe.State { + case SafeStateApproved, SafeStateClosed: + default: + return node.failRequest(ctx, req, "") + } + + extra := req.ExtraBytes() + if len(extra) != 48 { + return node.failRequest(ctx, req, "") + } + var ref crypto.Hash + copy(ref[:], extra[16:]) + raw := node.readStorageExtraFromObserver(ctx, ref) + + t, err := sg.TransactionFromBytes(raw) + if err != nil { + panic(err) + } + + signed := solana.CheckTransactionSignedBy(t, sg.MPK(safe.Observer)) + logger.Printf("solana.CheckTransactionSignedBy(%v, %s) => %t", t, safe.Observer, signed) + if !signed { + return node.failRequest(ctx, req, "") + } + + sbm, err := node.store.ReadAllSolanaTokenBalancesMap(ctx, safe.Address) + logger.Printf("store.ReadAllSolanaTokenBalancesMap(%s) => %v %v", safe.Address, sbm, err) + if err != nil { + panic(err) + } + + outputs := solana.ExtractOutputs(t) + if len(outputs) != len(sbm) { + return node.failRequest(ctx, req, "") + } + + destination := outputs[0].Destination + if destination == safe.Address { + return node.failRequest(ctx, req, "") + } + + for i, o := range outputs { + if destination != o.Destination { + logger.Printf("invalid close outputs destination: %d, %v", i, o) + return node.failRequest(ctx, req, "") + } + + sbb := sbm[o.TokenAddress].BigBalance() + if sbb.Cmp(o.Amount) != 0 { + logger.Printf("inconsistent amount between %s balance and output: %d, %d", o.TokenAddress, sbb, o.Amount) + return node.failRequest(ctx, req, "") + } + } + + count, err := node.store.CountUnfinishedTransactionsByHolder(ctx, safe.Holder) + logger.Printf("store.CountUnfinishedTransactionsByHolder(%s) => %d %v", safe.Holder, count, err) + if err != nil { + panic(err) + } + + rid, err := uuid.FromBytes(extra[:16]) + if err != nil { + return node.failRequest(ctx, req, "") + } + if rid.String() == uuid.Nil.String() { + if count != 0 { + return node.failRequest(ctx, req, "") + } + txs, asset := node.closeSolanaAccountWithHolder(ctx, req, safe, raw) + logger.Printf("node.closeSolanaAccountWithHolder(%v, %s) => %v %s", req, destination, txs, asset) + return txs, asset + } + + if count != 1 { + return node.failRequest(ctx, req, "") + } + + tx, err := node.store.ReadTransactionByRequestId(ctx, rid.String()) + if err != nil { + panic(fmt.Errorf("store.ReadTransactionByRequestId(%v) => %s %v", req, rid.String(), err)) + } else if tx == nil { + return node.failRequest(ctx, req, "") + } else if tx.State == common.RequestStateDone { + return node.failRequest(ctx, req, "") + } else if tx.Holder != req.Holder { + return node.failRequest(ctx, req, "") + } + + b := common.DecodeHexOrPanic(tx.RawTransaction) + proposedTx := common.Must(sg.TransactionFromBytes(b)) + if got, want := common.Must(t.Message.MarshalBinary()), common.Must(proposedTx.Message.MarshalBinary()); !bytes.Equal(got, want) { + logger.Printf("Inconsistent safe tx message: %x %x", got, want) + return node.failRequest(ctx, req, "") + } + + sr := &store.SignatureRequest{ + TransactionHash: tx.TransactionHash, + InputIndex: 0, + Signer: safe.Signer, + Curve: req.Curve, + Message: hex.EncodeToString(common.Must(t.Message.MarshalBinary())), + State: common.RequestStateInitial, + CreatedAt: req.CreatedAt, + UpdatedAt: req.CreatedAt, + } + sr.RequestId = common.UniqueId(req.Id, sr.Message) + + txs := node.buildSignerSignRequests(ctx, req, []*store.SignatureRequest{sr}, safe.Path) + if len(txs) == 0 { + return node.failRequest(ctx, req, "") + } + + signedRaw := hex.EncodeToString(common.Must(t.MarshalBinary())) + if safe.State == SafeStateApproved { + err = node.store.CloseAccountBySignatureRequestsWithRequest(ctx, []*store.SignatureRequest{sr}, tx.TransactionHash, signedRaw, req, txs) + logger.Printf("store.CloseAccountBySignatureRequestsWithRequest(%s, %v, %v) => %v", tx.TransactionHash, sr, req, err) + if err != nil { + panic(fmt.Errorf("store.WriteSignatureRequestsWithRequest(%s) => %v", tx.TransactionHash, err)) + } + } else { + err = node.store.WriteSignatureRequestsWithRequest(ctx, []*store.SignatureRequest{sr}, tx.TransactionHash, signedRaw, req, txs) + logger.Printf("store.WriteSignatureRequestsWithRequest(%s, %d, %v) => %v", tx.TransactionHash, 1, req, err) + if err != nil { + panic(fmt.Errorf("store.WriteSignatureRequestsWithRequest(%s) => %v", tx.TransactionHash, err)) + } + } + return txs, "" +} + +func (node *Node) closeSolanaAccountWithHolder(ctx context.Context, req *common.Request, safe *store.Safe, raw []byte) ([]*mtg.Transaction, string) { + t := common.Must(sg.TransactionFromBytes(raw)) + signedByHolder := solana.CheckTransactionSignedBy(t, sg.MPK(safe.Holder)) + logger.Printf("node.checkSolanaTransactionSignedBy(%v, %s) => %t", t, safe.Holder, signedByHolder) + if !signedByHolder { + return node.failRequest(ctx, req, "") + } + + outputs := solana.ExtractOutputs(t) + recipients := make([]map[string]string, len(outputs)) + for i, out := range outputs { + switch out.Destination { + case safe.Address, solana.SolanaEmptyAddress: + logger.Printf("invalid output destination: %s, %s", out.Destination, safe.Address) + return node.failRequest(ctx, req, "") + } + + decimals := int32(solana.NativeTokenDecimals) + if out.TokenAddress != solana.SolanaEmptyAddress { + assetId := solana.GenerateAssetId(out.TokenAddress) + asset, err := node.store.ReadAssetMeta(ctx, assetId) + logger.Printf("store.ReadAssetMeta(%s) => %v %v", assetId, asset, err) + if err != nil { + panic(err) + } + decimals = int32(asset.Decimals) + } + + amt := decimal.NewFromBigInt(out.Amount, -decimals) + r := map[string]string{ + "receiver": out.Destination, "amount": amt.String(), + } + r["token"] = out.TokenAddress + recipients[i] = r + } + + data := common.MarshalJSONOrPanic(recipients) + + tx := &store.Transaction{ + TransactionHash: t.Message.RecentBlockhash.String(), + RawTransaction: hex.EncodeToString(raw), + Holder: req.Holder, + Chain: safe.Chain, + State: common.RequestStateDone, + Data: string(data), + RequestId: req.Id, + CreatedAt: req.CreatedAt, + UpdatedAt: req.CreatedAt, + } + + stx := node.buildStorageTransaction(ctx, req, []byte(common.Base91Encode(raw))) + if stx == nil { + return node.failRequest(ctx, req, "") + } + txs := []*mtg.Transaction{stx} + + id := common.UniqueId(tx.TransactionHash, stx.TraceId) + typ := byte(common.ActionSolanaSafeApproveTransaction) + crv := common.SafeChainCurve(safe.Chain) + tt := node.buildObserverResponseWithStorageTraceId(ctx, id, req.Output, typ, crv, stx.TraceId) + if tt == nil { + return node.failRequest(ctx, req, "") + } + txs = append(txs, tt) + + if err := node.store.CloseAccountByTransactionWithRequest(ctx, tx, nil, common.RequestStateDone, txs, req); err != nil { + panic(err) + } + return txs, "" +} + +func (node *Node) processSolanaSafeProposeAccount(ctx context.Context, req *common.Request) ([]*mtg.Transaction, string) { + if req.Role != common.RequestRoleHolder { + panic(req.Role) + } + + switch req.Curve { + case common.CurveEdwards25519Default: + default: + panic(req.Curve) + } + + rce := req.ExtraBytes() + ver, _ := node.group.ReadKernelTransactionUntilSufficient(ctx, req.MixinHash.String()) + if len(rce) == 32 && len(ver.References) == 1 && bytes.Equal(ver.References[0][:], rce) { + stx, _ := node.group.ReadKernelTransactionUntilSufficient(ctx, ver.References[0].String()) + rce = stx.Extra + } + + arp, err := req.ParseMixinRecipient(ctx, node.mixin, rce) + logger.Printf("req.ParseMixinRecipient(%v) => %v %v", req, arp, err) + if err != nil { + return node.failRequest(ctx, req, "") + } + + chain := common.SafeCurveChain(req.Curve) + + plan, err := node.store.ReadLatestOperationParams(ctx, chain, req.CreatedAt) + logger.Printf("store.ReadLatestOperationParams(%d) => %v %v", chain, plan, err) + if err != nil { + panic(fmt.Errorf("node.ReadLatestOperationParams(%d) => %v", chain, err)) + } else if plan == nil || !plan.OperationPriceAmount.IsPositive() { + return node.refundAndFailRequest(ctx, req, arp.Receivers, int(arp.Threshold)) + } + if req.AssetId != plan.OperationPriceAsset { + return node.failRequest(ctx, req, "") + } + if req.Amount.Cmp(plan.OperationPriceAmount) < 0 { + return node.failRequest(ctx, req, "") + } + + safe, err := node.store.ReadSafe(ctx, req.Holder) + if err != nil { + panic(fmt.Errorf("store.ReadSafe(%s) => %v", req.Holder, err)) + } else if safe != nil { + // safe already exists + return node.failRequest(ctx, req, "") + } + + if old, err := node.store.ReadSafeProposal(ctx, req.Id); err != nil { + panic(fmt.Errorf("store.ReadSafeProposal(%s) => %v", req.Id, err)) + } else if old != nil { + // safe proposal already exists + return node.failRequest(ctx, req, "") + } + + signer, observer, err := node.store.AssignSignerAndObserverToHolder(ctx, req, SafeKeyBackupMaturity, arp.Observer) + logger.Printf("store.AssignSignerAndObserverToHolder(%s) => %s %s %v", req.Holder, signer, observer, err) + if err != nil { + panic(fmt.Errorf("store.AssignSignerAndObserverToHolder(%v) => %v", req, err)) + } + + if signer == "" || observer == "" { + return node.refundAndFailRequest(ctx, req, arp.Receivers, int(arp.Threshold)) + } + + if arp.Observer != "" && arp.Observer != observer { + panic(fmt.Errorf("store.AssignSignerAndObserverToHolder(%v) => %v %s", req, arp, observer)) + } + + if !common.CheckUnique(req.Holder, signer, observer) { + return node.refundAndFailRequest(ctx, req, arp.Receivers, int(arp.Threshold)) + } + + tx := solana.BuildSquadsSafe(solana.BuildSquadsSafeParams{ + Members: []sg.PublicKey{solana.MPK(req.Holder), solana.MPK(signer), solana.MPK(observer)}, + Creator: solana.MPK(req.Holder), + Nonce: arp.NonceAccount, + BlockHash: arp.BlockHash, + Payer: arp.PayerAccount, + Threshold: 2, + }) + + address := solana.GetAuthorityAddressFromCreateTx(tx) + if address.IsZero() { + panic("solana.GetAuthorityAddressFromCreateTx(tx) => zero") + } + + if old, err := node.store.ReadSafeProposalByAddress(ctx, address.String()); err != nil { + panic(fmt.Errorf("store.ReadSafeProposalByAddress(%s) => %v", address, err)) + } else if old != nil { + return node.failRequest(ctx, req, "") + } + + extra, err := tx.MarshalBinary() + if err != nil { + panic(err) + } + + stx := node.buildStorageTransaction(ctx, req, []byte(common.Base91Encode(extra))) + if stx == nil { + return node.refundAndFailRequest(ctx, req, arp.Receivers, int(arp.Threshold)) + } + txs := []*mtg.Transaction{stx} + + typ := byte(common.ActionSolanaSafeProposeAccount) + crv := common.SafeChainCurve(chain) + tt := node.buildObserverResponseWithStorageTraceId(ctx, req.Id, req.Output, typ, crv, stx.TraceId) + if tt == nil { + return node.refundAndFailRequest(ctx, req, arp.Receivers, int(arp.Threshold)) + } + txs = append(txs, tt) + + sp := &store.SafeProposal{ + RequestId: req.Id, + Chain: chain, + Holder: req.Holder, + Signer: signer, + Observer: observer, + Timelock: arp.Timelock, + Path: solanaDefaultDerivationPath(), + Address: address.String(), + Extra: extra, + Receivers: arp.Receivers, + Threshold: arp.Threshold, + CreatedAt: req.CreatedAt, + UpdatedAt: req.CreatedAt, + } + if err := node.store.WriteSafeProposalWithRequest(ctx, sp, txs, req); err != nil { + panic(err) + } + return txs, "" +} + +func (node *Node) processSolanaSafeApproveAccount(ctx context.Context, req *common.Request) ([]*mtg.Transaction, string) { + if req.Role != common.RequestRoleObserver { + panic(req.Role) + } + + if req.Curve != common.CurveEdwards25519Default { + panic(req.Curve) + } + + if old, err := node.store.ReadSafe(ctx, req.Holder); err != nil { + panic(fmt.Errorf("store.ReadSafe(%s) => %v", req.Holder, err)) + } else if old != nil && old.State != common.RequestStatePending { + return node.failRequest(ctx, req, "") + } + + chain := common.SafeCurveChain(req.Curve) + safeAssetId := node.getBondAssetId(ctx, node.conf.PolygonKeeperDepositEntry, common.SafeSolanaChainId, req.Holder) + + extra := req.ExtraBytes() + if len(extra) < 16+sg.SignatureLength { + return node.failRequest(ctx, req, "") + } + rid, err := uuid.FromBytes(extra[:16]) + if err != nil { + return node.failRequest(ctx, req, "") + } + + sp, err := node.store.ReadSafeProposal(ctx, rid.String()) + if err != nil { + panic(fmt.Errorf("store.ReadSafeProposal(%v) => %s %v", req, rid.String(), err)) + } else if sp == nil { + return node.failRequest(ctx, req, "") + } else if sp.Holder != req.Holder { + return node.failRequest(ctx, req, "") + } else if sp.Chain != chain { + return node.failRequest(ctx, req, "") + } + + tx, err := sg.TransactionFromBytes(sp.Extra) + if err != nil { + panic(err) + } + + holder := solana.MPK(req.Holder) + sig := sg.SignatureFromBytes(extra[16:]) + err = solana.AddSignature(tx, holder, sig) + logger.Printf("solana.AddSignature(%v) => %v", req, err) + if err != nil { + return node.failRequest(ctx, req, "") + } + + nextExtra, err := tx.MarshalBinary() + if err != nil { + panic(err) + } + + spr, err := node.store.ReadRequest(ctx, sp.RequestId) + if err != nil { + panic(fmt.Errorf("store.ReadSafeProposal(%s) => %v", sp.RequestId, err)) + } + + stx := node.buildStorageTransaction(ctx, req, []byte(common.Base91Encode(nextExtra))) + if stx == nil { + return node.failRequest(ctx, req, "") + } + txs := []*mtg.Transaction{stx} + + typ := byte(common.ActionSolanaSafeApproveAccount) + crv := common.SafeChainCurve(sp.Chain) + t := node.buildObserverResponseWithAssetAndStorageTraceId(ctx, req.Id, req.Output, typ, crv, spr.AssetId, spr.Amount.String(), stx.TraceId) + if t == nil { + return node.failRequest(ctx, req, spr.AssetId) + } + txs = append(txs, t) + + safe := &store.Safe{ + Holder: sp.Holder, + Chain: sp.Chain, + Signer: sp.Signer, + Observer: sp.Observer, + Timelock: sp.Timelock, + Path: sp.Path, + Address: sp.Address, + Extra: sp.Extra, + Receivers: sp.Receivers, + Threshold: sp.Threshold, + RequestId: req.Id, + State: SafeStateApproved, + Nonce: 0, + SafeAssetId: safeAssetId, + CreatedAt: req.CreatedAt, + UpdatedAt: req.CreatedAt, + } + + err = node.store.WriteSafeWithRequest(ctx, safe, txs, req) + if err != nil { + panic(err) + } + return txs, "" +} + +func (node *Node) processSolanaSafeProposeTransaction(ctx context.Context, req *common.Request) ([]*mtg.Transaction, string) { + if req.Role != common.RequestRoleHolder { + panic(req.Role) + } + + safe, err := node.store.ReadSafe(ctx, req.Holder) + if err != nil { + panic(fmt.Errorf("store.ReadSafe(%s) => %v", req.Holder, err)) + } + + chain := common.SafeCurveChain(req.Curve) + if safe.Chain != chain { + return node.failRequest(ctx, req, "") + } + + if safe == nil || safe.Chain != chain { + return node.failRequest(ctx, req, "") + } + if safe.State != SafeStateApproved { + return node.failRequest(ctx, req, "") + } + + pendings, err := node.store.ReadUnfinishedTransactionsByHolder(ctx, safe.Holder) + logger.Printf("store.ReadUnfinishedTransactionsByHolder(%s) => %v %v", safe.Holder, len(pendings), err) + if len(pendings) > 0 { + return node.failRequest(ctx, req, "") + } + + meta, err := node.fetchAssetMeta(ctx, req.AssetId) + logger.Printf("node.fetchAssetMeta(%s) => %v %v", req.AssetId, meta, err) + if err != nil { + panic(fmt.Errorf("node.fetchAssetMeta(%s) => %v", req.AssetId, err)) + } + if meta.Chain != common.SafeChainPolygon { + return node.failRequest(ctx, req, "") + } + + deployed, err := abi.CheckFactoryAssetDeployed(node.conf.PolygonRPC, meta.AssetKey) + logger.Printf("abi.CheckFactoryAssetDeployed(%s) => %v %v", meta.AssetKey, deployed, err) + if err != nil || deployed.Sign() <= 0 { + panic(fmt.Errorf("api.CheckFatoryAssetDeployed(%s) => %v", meta.AssetKey, err)) + } + id := uuid.Must(uuid.FromBytes(deployed.Bytes())) + + plan, err := node.store.ReadLatestOperationParams(ctx, safe.Chain, req.CreatedAt) + logger.Printf("store.ReadLatestOperationParams(%d) => %v %v", safe.Chain, plan, err) + if err != nil { + panic(fmt.Errorf("store.ReadLatestOperationParams(%d) => %v", safe.Chain, err)) + } else if plan == nil || !plan.TransactionMinimum.IsPositive() { + return node.refundAndFailRequest(ctx, req, safe.Receivers, int(safe.Threshold)) + } + if req.Amount.Cmp(plan.TransactionMinimum) < 0 { + return node.failRequest(ctx, req, "") + } + + entry := node.fetchBondAssetReceiver(ctx, safe.Address, id.String()) + safeAssetId := node.getBondAssetId(ctx, entry, id.String(), req.Holder) + logger.Printf("node.getBondAssetId(%s, %s, %s) => %s", entry, id.String(), req.Holder, safeAssetId) + if req.AssetId != safeAssetId { + panic(req.AssetId) + } + + txReq, err := solana.DecodeTransactionRequest(req.ExtraBytes()) + if err != nil { + logger.Printf("solana.DecodeTransactionRequest(%v) => %v", req, err) + return node.failRequest(ctx, req, "") + } + + if txReq.RequestID.IsNil() { + return node.failRequest(ctx, req, "") + } + + info, err := node.store.ReadNetworkInfo(ctx, txReq.RequestID.String()) + logger.Printf("store.ReadNetworkInfo(%s) => %v %v", txReq.RequestID.String(), info, err) + if err != nil { + panic(fmt.Errorf("store.ReadNetworkInfo(%s) => %v", txReq.RequestID.String(), err)) + } + if info == nil || info.Chain != safe.Chain { + return node.failRequest(ctx, req, "") + } + + balance, err := node.store.ReadSolanaBalance(ctx, safe.Address, id.String(), safeAssetId) + logger.Printf("store.ReadSolanaBalance(%s, %s) => %v %v", safe.Address, id.String(), balance, err) + if err != nil { + panic(err) + } + if balance.SafeAssetId != req.AssetId { + panic(balance.SafeAssetId) + } + + if balance.SafeAssetId != req.AssetId { + panic(balance.SafeAssetId) + } + + decimals := int32(solana.NativeTokenDecimals) + if balance.AssetAddress != solana.SolanaEmptyAddress { + asset, err := node.store.ReadAssetMeta(ctx, id.String()) + logger.Printf("store.ReadAssetMeta(%s) => %v %v", id.String(), asset, err) + if err != nil { + panic(err) + } + + decimals = int32(asset.Decimals) + } + + var outputs []*solana.Output + ver, _ := node.group.ReadKernelTransactionUntilSufficient(ctx, req.MixinHash.String()) + if len(ver.References) == 1 && ver.References[0].String() == hex.EncodeToString(txReq.Extra[:]) { + stx, _ := node.group.ReadKernelTransactionUntilSufficient(ctx, ver.References[0].String()) + var recipients [][2]string + err = json.Unmarshal(stx.Extra, &recipients) + if err != nil { + return node.failRequest(ctx, req, "") + } + + for _, rp := range recipients { + amt, err := decimal.NewFromString(rp[1]) + if err != nil { + return node.failRequest(ctx, req, "") + } + + if amt.Cmp(plan.TransactionMinimum) < 0 { + return node.failRequest(ctx, req, "") + } + + o := &solana.Output{ + TokenAddress: balance.AssetAddress, + Destination: rp[0], + Amount: ethereum.ParseAmount(amt.String(), decimals), + } + outputs = append(outputs, o) + } + } else { + outputs = []*solana.Output{{ + TokenAddress: balance.AssetAddress, + Destination: sg.PublicKeyFromBytes(txReq.Extra[:]).String(), + Amount: ethereum.ParseAmount(req.Amount.String(), decimals), + }} + } + + total := decimal.Zero + recipients := make([]map[string]string, len(outputs)) + for i, out := range outputs { + dest, err := sg.PublicKeyFromBase58(out.Destination) + if err != nil || dest.IsZero() || dest.String() == safe.Address { + logger.Printf("invalid output destination: %s, %s", out.Destination, safe.Address) + return node.failRequest(ctx, req, "") + } + + amt := decimal.NewFromBigInt(out.Amount, decimals) + r := map[string]string{"receiver": out.Destination, "amount": amt.String()} + if out.TokenAddress != solana.SolanaEmptyAddress { + r["token"] = out.TokenAddress + } + recipients[i] = r + total = total.Add(amt) + } + + if len(outputs) > 256 || !total.Equal(req.Amount) { + return node.failRequest(ctx, req, "") + } + + switch txReq.Flag { + case common.FlagProposeNormalTransaction: + case common.FlagProposeRecoveryTransaction: + if len(outputs) != 1 { + logger.Printf("invalid recovery transaction outputs: %d", len(outputs)) + return node.failRequest(ctx, req, "") + } + + balances, err := node.store.ReadAllSolanaTokenBalances(ctx, safe.Address) + logger.Printf("store.ReadAllSolanaTokenBalances(%s) => %v %v", safe.Address, balances, err) + if err != nil { + panic(err) + } + + for _, b := range balances { + if b.AssetId == balance.AssetId || b.BigBalance().Cmp(big.NewInt(0)) == 0 { + continue + } + + output := &solana.Output{ + TokenAddress: b.AssetAddress, + Destination: sg.PublicKeyFromBytes(txReq.Extra[:]).String(), + Amount: b.BigBalance(), + } + outputs = append(outputs, output) + + asset, err := node.store.ReadAssetMeta(ctx, b.AssetId) + logger.Printf("store.ReadAssetMeta(%s) => %v %v", b.AssetId, asset, err) + if err != nil { + panic(err) + } + + amt := decimal.NewFromBigInt(output.Amount, int32(-asset.Decimals)) + r := map[string]string{ + "receiver": output.Destination, + "amount": amt.String(), + } + if output.TokenAddress != solana.SolanaEmptyAddress { + r["token"] = output.TokenAddress + } + recipients = append(recipients, r) + } + default: + logger.Printf("invalid transaction flag: %d", txReq.Flag) + return node.failRequest(ctx, req, "") + } + + voters := []sg.PublicKey{sg.MPK(safe.Holder), sg.MPK(safe.Holder), sg.MPK(safe.Signer)} + t, err := solana.CreateTransactionFromOutputs(txReq, outputs, voters, sg.MPK(safe.Holder), uint32(safe.Nonce+1)) + logger.Printf("solana.CreateTransactionFromOutputs(%v) => %v %v", txReq, t, err) + if err != nil { + panic(err) + } + + extra, err := t.MarshalBinary() + logger.Printf("solana.Transaction.MarshalBinary(%v) => %v %v", t, extra, err) + if err != nil { + panic(err) + } + + stx := node.buildStorageTransaction(ctx, req, []byte(common.Base91Encode(extra))) + if stx == nil { + return node.failRequest(ctx, req, "") + } + txs := []*mtg.Transaction{stx} + + typ := byte(common.ActionSolanaSafeProposeTransaction) + crv := common.SafeChainCurve(safe.Chain) + tt := node.buildObserverResponseWithStorageTraceId(ctx, req.Id, req.Output, typ, crv, stx.TraceId) + if tt == nil { + return node.failRequest(ctx, req, "") + } + txs = append(txs, tt) + + data := common.MarshalJSONOrPanic(recipients) + tx := &store.Transaction{ + TransactionHash: t.Message.RecentBlockhash.String(), + RawTransaction: hex.EncodeToString(extra), + Holder: req.Holder, + Chain: safe.Chain, + AssetId: id.String(), + State: common.RequestStateInitial, + Data: string(data), + RequestId: req.Id, + CreatedAt: req.CreatedAt, + UpdatedAt: req.CreatedAt, + } + err = node.store.WriteTransactionWithRequest(ctx, tx, nil, txs, req) + if err != nil { + panic(err) + } + return txs, "" +} + +func (node *Node) processSolanaSafeApproveTransaction(ctx context.Context, req *common.Request) ([]*mtg.Transaction, string) { + if req.Role != common.RequestRoleObserver { + panic(req.Role) + } + + safe, err := node.store.ReadSafe(ctx, req.Holder) + if err != nil { + panic(fmt.Errorf("store.ReadSafe(%s) => %v", req.Holder, err)) + } + if safe == nil || safe.Chain != common.SafeChainSolana { + return node.failRequest(ctx, req, "") + } + + extra := req.ExtraBytes() + if len(extra) != 48 { + return node.failRequest(ctx, req, "") + } + rid, err := uuid.FromBytes(extra[:16]) + if err != nil { + return node.failRequest(ctx, req, "") + } + tx, err := node.store.ReadTransactionByRequestId(ctx, rid.String()) + if err != nil { + panic(fmt.Errorf("store.ReadTransactionByRequestId(%v) => %s %v", req, rid.String(), err)) + } else if tx == nil { + return node.failRequest(ctx, req, "") + } else if tx.State == common.RequestStateDone { + return node.failRequest(ctx, req, "") + } else if tx.Holder != req.Holder { + return node.failRequest(ctx, req, "") + } + + var ref crypto.Hash + copy(ref[:], extra[16:]) + raw := node.readStorageExtraFromObserver(ctx, ref) + t, err := sg.TransactionFromBytes(raw) + logger.Printf("ethereum.UnmarshalSafeTransaction(%x) => %v %v", raw, t, err) + if err != nil { + panic(err) + } + + signed := solana.CheckTransactionSignedBy(t, sg.MPK(safe.Holder)) + logger.Printf("solana.CheckTransactionSignedBy(%v, %s) => %t", t, safe.Holder, signed) + if !signed { + return node.failRequest(ctx, req, "") + } + + msg, err := t.Message.MarshalBinary() + logger.Printf("solana.Transaction.MarshalBinary(%v) => %v %v", t, msg, err) + if err != nil { + panic(err) + } + + sr := &store.SignatureRequest{ + TransactionHash: tx.TransactionHash, + InputIndex: 0, + Signer: safe.Signer, + Curve: req.Curve, + Message: hex.EncodeToString(msg), + State: common.RequestStateInitial, + CreatedAt: req.CreatedAt, + UpdatedAt: req.CreatedAt, + } + + sr.RequestId = common.UniqueId(req.Id, sr.Message) + txs := node.buildSignerSignRequests(ctx, req, []*store.SignatureRequest{sr}, safe.Path) + if len(txs) == 0 { + return node.failRequest(ctx, req, "") + } + + err = node.store.WriteSignatureRequestsWithRequest(ctx, []*store.SignatureRequest{sr}, tx.TransactionHash, hex.EncodeToString(raw), req, txs) + logger.Printf("store.WriteSignatureRequestsWithRequest(%s, %d, %v) => %v", tx.TransactionHash, 1, req, err) + if err != nil { + panic(err) + } + return txs, "" +} + +func (node *Node) processSolanaSafeSignatureResponse(ctx context.Context, req *common.Request, safe *store.Safe, tx *store.Transaction, old *store.SignatureRequest) ([]*mtg.Transaction, string) { + if req.Role != common.RequestRoleSigner { + panic(req.Role) + } + + if safe.State != SafeStateApproved { + return node.failRequest(ctx, req, "") + } + + // verify signature from req + { + msg := common.DecodeHexOrPanic(old.Message) + if err := solana.VerifyMessageSignature(safe.Signer, msg, req.ExtraBytes()); err != nil { + logger.Printf("solana.signer.Verify(%v) => %v", req, err) + return node.failRequest(ctx, req, "") + } + } + + err := node.store.FinishSignatureRequest(ctx, req) + logger.Printf("store.FinishSignatureRequest(%s) => %v", req.Id, err) + if err != nil { + panic(fmt.Errorf("store.FinishSignatureRequest(%s) => %v", req.Id, err)) + } + + rawB := common.DecodeHexOrPanic(tx.RawTransaction) + t, err := sg.TransactionFromBytes(rawB) + logger.Printf("solana.TransactionFromBytes(%v) => %v %v", rawB, t, err) + if err != nil { + panic(err) + } + + requests, err := node.store.ListAllSignaturesForTransaction(ctx, old.TransactionHash, common.RequestStatePending) + logger.Printf("store.ListAllSignaturesForTransaction(%s) => %d %v", old.TransactionHash, len(requests), err) + if err != nil { + panic(fmt.Errorf("store.ListAllSignaturesForTransaction(%s) => %v", old.TransactionHash, err)) + } + if len(requests) != 1 { + panic(fmt.Errorf("invalid signature requests len: %d", len(requests))) + } + + sig := sg.SignatureFromBytes(common.DecodeHexOrPanic(requests[0].Signature.String)) + if err := solana.AddSignature(t, sg.MPK(safe.Signer), sig); err != nil { + panic(err) + } + + raw, err := t.MarshalBinary() + logger.Printf("solana.Transaction.MarshalBinary(%v) => %v %v", t, raw, err) + if err != nil { + panic(err) + } + + sbm, err := node.store.ReadAllSolanaTokenBalancesMap(ctx, safe.Address) + logger.Printf("store.ReadAllSolanaTokenBalancesMap(%s) => %v %v", safe.Address, sbm, err) + if err != nil { + panic(err) + } + + outputs := solana.ExtractOutputs(t) + for _, o := range outputs { + closeBalance := big.NewInt(0).Sub(sbm[o.TokenAddress].BigBalance(), o.Amount) + if closeBalance.Cmp(big.NewInt(0)) < 0 { + logger.Printf("safe %s close balance %d lower than 0", safe.Address, closeBalance) + return node.failRequest(ctx, req, "") + } + sbm[o.TokenAddress].UpdateBalance(new(big.Int).Neg(o.Amount)) + } + + stx := node.buildStorageTransaction(ctx, req, []byte(common.Base91Encode(raw))) + if stx == nil { + return node.failRequest(ctx, req, "") + } + txs := []*mtg.Transaction{stx} + + id := common.UniqueId(old.TransactionHash, stx.TraceId) + typ := byte(common.ActionSolanaSafeApproveTransaction) + crv := common.SafeChainCurve(safe.Chain) + tt := node.buildObserverResponseWithStorageTraceId(ctx, id, req.Output, typ, crv, stx.TraceId) + if tt == nil { + return node.failRequest(ctx, req, "") + } + txs = append(txs, tt) + + err = node.store.FinishTransactionSignaturesWithRequest(ctx, old.TransactionHash, hex.EncodeToString(raw), req, 0, safe, sbm, txs) + logger.Printf("store.FinishTransactionSignaturesWithRequest(%s, %s, %v) => %v", old.TransactionHash, raw, req, err) + if err != nil { + panic(err) + } + return txs, "" +} diff --git a/keeper/solana_test.go b/keeper/solana_test.go new file mode 100644 index 00000000..1d155712 --- /dev/null +++ b/keeper/solana_test.go @@ -0,0 +1,389 @@ +package keeper + +import ( + "context" + "encoding/base64" + "encoding/binary" + "encoding/hex" + "encoding/json" + "fmt" + "os" + "testing" + "time" + + mc "github.com/MixinNetwork/mixin/crypto" + "github.com/MixinNetwork/mixin/logger" + "github.com/MixinNetwork/safe/apps/solana" + "github.com/MixinNetwork/safe/common" + "github.com/MixinNetwork/safe/signer" + "github.com/MixinNetwork/trusted-group/mtg" + sg "github.com/gagliardetto/solana-go" + "github.com/gofrs/uuid/v5" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/require" +) + +var ( + testSolanaKeyHolder = sg.MustPrivateKeyFromBase58("3Md1AnGnmDxSwrDjz9cbwRfGAVNvPR2pH9SGftLdeFZEx7HXSTA8seXahRH3KbjbXAAXRpqqqvLFyugLLjxE3HW8") + testSolanaKeyObserver = sg.MustPrivateKeyFromBase58("3NZzws9DKasavm5E6mERiiiB5qqKAze7eznzxJe9Hkq6fvSwizC9M644BToWPZnJmNPGxqfRcYaEQTtspa1wkeQ1") + testSolanaKeyDummyHolder = sg.MustPrivateKeyFromBase58("2UVbe4ZGaX5r9oQWSSLAMHB9m6C7iAoDN8ijXSmATgv6BaBuhqk9eGNH7ALV6WL3PMFzhaMcZxtVzQZbwcQMKRHM") + + testSolanaNonceAccount = sg.MPK("FHwzoFkcHxc2xMgTPWjvyra87DBQqwGsS78yCx2EUboh") + testSolanaBlockhash = sg.MPK("3qmjGHDNkk6QC5G4P3SjJauD7Wgdwtgbw9p8fp4dPTqC") + testSolanaPayerAccount = sg.MPK("FB1J65JHc1nkgSiuEpSW6fD65MJw6VBT7dN6AyMpGU9B") + + testSolanaBondAssetId = "08823f4a-6fd4-311e-8ddd-9478e163cf91" + testSolanaUSDTAssetId = "218bc6f4-7927-3f8e-8568-3a3725b74361" + testSolanaUSDTBondAssetId = "edc249f5-d792-3091-a359-23c67ce0d595" + testSolanaUSDTAddress = "H7UPvz5Gouue7Joihvu9jbX4CM4jjxTh3c57FZ2Pkhva" + testSolanaTransactionReceiver = sg.MPK("3Maas91CwJdYr1wk59buvPEnBx3dkLQpQNopfPfwUARe") +) + +func TestSolanaKeeper(t *testing.T) { + require := require.New(t) + ctx, node, db, _, signers := testSolanaPrepare(require) + + output, err := testWriteOutput(ctx, db, node.conf.AppId, testSolanaBondAssetId, testGenerateDummyExtra(node), sequence, decimal.NewFromInt(100000000000000)) + require.Nil(err) + action := &mtg.Action{ + UnifiedOutput: *output, + } + node.ProcessOutput(ctx, action) + testSolanaObserverHolderDeposit(ctx, require, node, "51Vzfuah4LoAPwpGWEJekrFpjbUeoNa4f85SFQHN1p7pKAMBYTk6j28tpaB6oudTu3tndsiinQ7e7rgaQQx4tG1Z", common.SafeSolanaChainId, testSolanaUSDTAddress, "100000000000000") + + txHash := testSolanaProposeTransaction(ctx, require, node, testSolanaBondAssetId) + testSolanaApproveTransaction(ctx, require, node, txHash, solana.SolanaMixinChainId, signers) +} + +func testSolanaPrepare(require *require.Assertions) (context.Context, *Node, *mtg.SQLite3Store, string, []*signer.Node) { + logger.SetLevel(logger.INFO) + ctx, signers, _ := signer.TestPrepare(require) + public := signer.TestFROSTPrepareKeys(ctx, require, signers, common.CurveEdwards25519Default) + + // placeholder for chain code + chainCode := [32]byte{} + + root, err := os.MkdirTemp("", "safe-keeper-test-") + require.Nil(err) + node, db := testBuildNode(ctx, require, root) + require.NotNil(node) + timestamp, err := node.timestamp(ctx) + require.Nil(err) + require.Equal(node.conf.MTG.Genesis.Epoch, timestamp) + testSpareKeys(ctx, require, node, 0, 0, 0, common.CurveEdwards25519Default) + + id := uuid.Must(uuid.NewV4()).String() + extra := append([]byte{common.RequestRoleSigner}, chainCode[:]...) + extra = append(extra, common.RequestFlagNone) + out := testBuildSignerOutput(node, id, public, common.OperationTypeKeygenOutput, extra, common.CurveEdwards25519Default) + testStep(ctx, require, node, out) + v, err := node.store.ReadProperty(ctx, id) + require.Nil(err) + require.Equal("", v) + testSpareKeys(ctx, require, node, 0, 1, 0, common.CurveEdwards25519Default) + + id = uuid.Must(uuid.NewV4()).String() + observer := hex.EncodeToString(testSolanaKeyObserver.PublicKey().Bytes()) + occ := make([]byte, 32) + extra = append([]byte{common.RequestRoleObserver}, occ...) + extra = append(extra, common.RequestFlagNone) + out = testBuildObserverRequest(node, id, observer, common.ActionObserverAddKey, extra, common.CurveEdwards25519Default) + testStep(ctx, require, node, out) + v, err = node.store.ReadProperty(ctx, id) + require.Nil(err) + require.Equal("", v) + testSpareKeys(ctx, require, node, 0, 1, 1, common.CurveEdwards25519Default) + + batch := byte(64) + id = uuid.Must(uuid.NewV4()).String() + dummy := hex.EncodeToString(testSolanaKeyHolder.PublicKey().Bytes()) + out = testBuildObserverRequest(node, id, dummy, common.ActionObserverRequestSignerKeys, []byte{batch}, common.CurveEdwards25519Default) + testStep(ctx, require, node, out) + signerMembers := node.GetSigners() + for i := byte(0); i < batch; i++ { + pid := common.UniqueId(id, fmt.Sprintf("%8d", i)) + pid = common.UniqueId(pid, fmt.Sprintf("MTG:%v:%d", signerMembers, node.signer.Genesis.Threshold)) + v, _ := node.store.ReadProperty(ctx, pid) + var om map[string]any + err = json.Unmarshal([]byte(v), &om) + require.Nil(err) + b, _ := hex.DecodeString(om["memo"].(string)) + b = common.AESDecrypt(node.signerAESKey[:], b) + o, err := common.DecodeOperation(b) + require.Nil(err) + require.Equal(pid, o.Id) + } + testSpareKeys(ctx, require, node, 0, 1, 1, common.CurveEdwards25519Default) + + for i := 0; i < 10; i++ { + testSolanaUpdateAccountPrice(ctx, require, node) + } + + rid, stx := testSolanaProposeAccount(ctx, require, node, public, observer) + testSpareKeys(ctx, require, node, 0, 0, 0, common.CurveEdwards25519Default) + testSolanaApproveAccount(ctx, require, node, rid, stx) + testSpareKeys(ctx, require, node, 0, 0, 0, common.CurveEdwards25519Default) + for i := 0; i < 10; i++ { + testSolanaUpdateNetworkStatus(ctx, require, node, 373789745, "BMgyjNfP89GiUZ4YbXrFHcWP797dAk8ZFRDZ31heKZzT") + } + + holder := hex.EncodeToString(testSolanaKeyHolder.PublicKey().Bytes()) + safe, err := node.store.ReadSafe(ctx, holder) + require.Nil(err) + require.NotNil(safe) + require.Equal(int64(0), safe.Nonce) + + return ctx, node, db, public, signers +} + +func testSolanaProposeTransaction(ctx context.Context, require *require.Assertions, node *Node, rid string) string { + holder := testSolanaKeyHolder.PublicKey().String() + info, err := node.store.ReadLatestNetworkInfo(ctx, common.SafeChainSolana, time.Now()) + require.Nil(err) + extra := []byte{0} + extra = append(extra, uuid.Must(uuid.FromString(info.RequestId)).Bytes()...) + extra = append(extra, testSolanaTransactionReceiver[:]...) + out := testBuildHolderRequest(node, rid, holder, common.ActionSolanaSafeProposeTransaction, testSolanaBondAssetId, extra, decimal.NewFromFloat(0.0001)) + testStep(ctx, require, node, out) + + b := testReadObserverResponse(ctx, require, node, rid, common.ActionSolanaSafeProposeTransaction) + t, err := sg.TransactionFromBytes(b) + require.Nil(err) + + outputs := solana.ExtractOutputs(t) + require.Len(outputs, 1) + amt := decimal.NewFromBigInt(outputs[0].Amount, -int32(solana.NativeTokenDecimals)) + require.Equal("0.0001", amt.String()) + require.Equal(testSolanaTransactionReceiver, outputs[0].Destination) + + stx, err := node.store.ReadTransaction(ctx, t.Message.RecentBlockhash.String()) + require.Nil(err) + + require.Equal(hex.EncodeToString(b), stx.RawTransaction) + data := fmt.Sprintf("[{\"amount\":\"0.0001\",\"receiver\":\"%s\"}]", testSolanaTransactionReceiver.String()) + require.Equal(data, stx.Data) + require.Equal(common.RequestStateInitial, stx.State) + + return stx.TransactionHash +} + +func testSolanaApproveTransaction(ctx context.Context, require *require.Assertions, node *Node, transactionHash, assetId string, signers []*signer.Node) { + id := uuid.Must(uuid.NewV4()).String() + + tx, err := node.store.ReadTransaction(ctx, transactionHash) + require.Nil(err) + require.Equal(common.RequestStateInitial, tx.State) + + raw, err := hex.DecodeString(tx.RawTransaction) + require.Nil(err) + t, err := sg.TransactionFromBytes(raw) + require.Nil(err) + + safe, err := node.store.ReadSafe(ctx, tx.Holder) + require.Nil(err) + + require.Nil(solana.Sign(t, testSolanaKeyHolder)) + + raw, err = t.MarshalBinary() + require.Nil(err) + ref := mc.Sha256Hash(raw) + err = node.store.WriteProperty(ctx, ref.String(), base64.RawURLEncoding.EncodeToString(raw)) + require.Nil(err) + + extra := uuid.Must(uuid.FromString(tx.RequestId)).Bytes() + extra = append(extra, ref[:]...) + + out := testBuildObserverRequest(node, id, safe.Holder, common.ActionSolanaSafeApproveTransaction, extra, common.CurveEdwards25519Default) + testStep(ctx, require, node, out) + + requests, err := node.store.ListAllSignaturesForTransaction(ctx, transactionHash, common.RequestStateInitial) + require.Nil(err) + require.Len(requests, 1) + tx, _ = node.store.ReadTransaction(ctx, transactionHash) + require.Equal(common.RequestStatePending, tx.State) + + msg, err := hex.DecodeString(requests[0].Message) + require.Nil(err) + out = testBuildSignerOutput(node, requests[0].RequestId, safe.Signer, common.OperationTypeSignInput, msg, common.CurveEdwards25519Default) + op := signer.TestProcessOutput(ctx, require, signers, out, requests[0].RequestId) + out = testBuildSignerOutput(node, requests[0].RequestId, safe.Signer, common.OperationTypeSignOutput, op.Extra, common.CurveEdwards25519Default) + testStep(ctx, require, node, out) + + tx, _ = node.store.ReadTransaction(ctx, transactionHash) + require.Equal(common.RequestStateDone, tx.State) + requests, _ = node.store.ListAllSignaturesForTransaction(ctx, transactionHash, common.RequestStateInitial) + require.Len(requests, 0) + requests, _ = node.store.ListAllSignaturesForTransaction(ctx, transactionHash, common.RequestStatePending) + require.Len(requests, 0) + requests, _ = node.store.ListAllSignaturesForTransaction(ctx, transactionHash, common.RequestStateDone) + require.Len(requests, 1) + tx, _ = node.store.ReadTransaction(ctx, transactionHash) + require.Equal(common.RequestStateDone, tx.State) + + safeAssetId := node.getBondAssetId(ctx, node.conf.PolygonKeeperDepositEntry, assetId, safe.Holder) + balance, err := node.store.ReadSolanaBalance(ctx, safe.Holder, assetId, safeAssetId) + require.Nil(err) + require.Equal(int64(0), balance.BigBalance().Int64()) + + safe, err = node.store.ReadSafe(ctx, tx.Holder) + require.Nil(err) + require.Equal(int64(2), safe.Nonce) +} + +func testSolanaObserverHolderDeposit(ctx context.Context, require *require.Assertions, node *Node, signature, assetId, assetAddress, balance string) { + id := uuid.Must(uuid.NewV4()).String() + amt, err := decimal.NewFromString(balance) + require.Nil(err) + + sig, err := sg.SignatureFromBase58(signature) + require.Nil(err) + + client := node.solanaClient() + rpcTx, err := client.RPCGetTransaction(ctx, signature) + require.Nil(err) + tx, err := rpcTx.Transaction.GetTransaction() + require.Nil(err) + meta := rpcTx.Meta + require.NotNil(meta) + + index := 0 + transfers, err := client.ExtractTransfersFromTransaction(ctx, tx, meta) + require.Nil(err) + + for _, transfer := range transfers { + if transfer.TokenAddress == assetAddress && transfer.Receiver == testSolanaTransactionReceiver.String() { + index = int(transfer.Index) + } + } + + extra := []byte{common.SafeChainSolana} + extra = append(extra, uuid.Must(uuid.FromString(assetId)).Bytes()...) + extra = append(extra, testSolanaTransactionReceiver[:]...) + extra = append(extra, sig[:]...) + extra = append(extra, sg.MPK(assetAddress).Bytes()...) + extra = binary.BigEndian.AppendUint64(extra, uint64(index)) + extra = append(extra, amt.BigInt().Bytes()...) + + holder := testSolanaKeyHolder.PublicKey() + safeAddress := solana.GetDefaultAuthorityPDA(solana.GetMultisigPDA(holder)).String() + + bondId := testDeployBondContract(ctx, require, node, safeAddress, assetId) + out := testBuildObserverRequest(node, id, holder.String(), common.ActionObserverHolderDeposit, extra, common.CurveEdwards25519Default) + testStep(ctx, require, node, out) + + safeAssetId := node.getBondAssetId(ctx, node.conf.PolygonKeeperDepositEntry, assetId, holder.String()) + require.Equal(bondId, safeAssetId) + safeBalance, err := node.store.ReadSolanaBalance(ctx, holder.String(), assetId, safeAssetId) + require.Nil(err) + require.Equal(balance, safeBalance.BigBalance().String()) +} + +func testSolanaUpdateAccountPrice(ctx context.Context, require *require.Assertions, node *Node) { + id := uuid.Must(uuid.NewV4()).String() + + extra := []byte{common.SafeChainSolana} + extra = append(extra, uuid.Must(uuid.FromString(testAccountPriceAssetId)).Bytes()...) + extra = binary.BigEndian.AppendUint64(extra, testAccountPriceAmount*100000000) + extra = binary.BigEndian.AppendUint64(extra, 10000) + dummy := hex.EncodeToString(testSolanaKeyHolder.PublicKey().Bytes()) + out := testBuildObserverRequest(node, id, dummy, common.ActionObserverSetOperationParams, extra, common.CurveEdwards25519Default) + testStep(ctx, require, node, out) + + plan, err := node.store.ReadLatestOperationParams(ctx, common.SafeChainSolana, time.Now()) + require.Nil(err) + require.Equal(testAccountPriceAssetId, plan.OperationPriceAsset) + require.Equal(fmt.Sprint(testAccountPriceAmount), plan.OperationPriceAmount.String()) + require.Equal("0.0001", plan.TransactionMinimum.String()) +} + +func testSolanaRecipient() []byte { + extra := binary.BigEndian.AppendUint16(nil, 0) + extra = append(extra, 1, 1) + id := uuid.FromStringOrNil(testSafeBondReceiverId) + extra = append(extra, id.Bytes()...) + extra = append(extra, testSolanaNonceAccount[:]...) + extra = append(extra, testSolanaBlockhash[:]...) + extra = append(extra, testSolanaPayerAccount[:]...) + return extra +} + +func testSolanaProposeAccount(ctx context.Context, require *require.Assertions, node *Node, signer, observer string) (string, *sg.Transaction) { + id := uuid.Must(uuid.NewV4()).String() + holder := hex.EncodeToString(testSolanaKeyHolder.PublicKey().Bytes()) + + extra := testSolanaRecipient() + price := decimal.NewFromFloat(testAccountPriceAmount) + out := testBuildHolderRequest(node, id, holder, common.ActionSolanaSafeProposeAccount, testAccountPriceAssetId, extra, price) + testStep(ctx, require, node, out) + b := testReadObserverResponse(ctx, require, node, id, common.ActionSolanaSafeProposeAccount) + stx, err := sg.TransactionFromBytes(b) + require.Nil(err) + + safeAddress := solana.GetDefaultAuthorityPDA(solana.GetMultisigPDA(testSolanaKeyHolder.PublicKey())).String() + require.Equal(safeAddress, solana.GetAuthorityAddressFromCreateTx(stx).String()) + + sp, err := node.store.ReadSafeProposal(ctx, id) + require.Nil(err) + require.Equal(id, sp.RequestId) + require.Equal(holder, sp.Holder) + require.Equal(signer, sp.Signer) + require.Equal(observer, sp.Observer) + require.Equal(safeAddress, sp.Address) + require.Equal(byte(1), sp.Threshold) + require.Len(sp.Receivers, 1) + require.Equal(testSafeBondReceiverId, sp.Receivers[0]) + + return id, stx +} + +func testSolanaApproveAccount(ctx context.Context, require *require.Assertions, node *Node, rid string, stx *sg.Transaction) { + approveRequestId := uuid.Must(uuid.NewV4()).String() + holder := hex.EncodeToString(testSolanaKeyHolder.PublicKey().Bytes()) + + safeAddress := solana.GetAuthorityAddressFromCreateTx(stx).String() + sp, err := node.store.ReadSafeProposalByAddress(ctx, safeAddress) + require.Nil(err) + + outputs := solana.ExtractOutputs(stx) + require.Len(outputs, 0) + + content, err := stx.Message.MarshalBinary() + require.Nil(err) + + signature, err := testSolanaKeyHolder.Sign(content) + require.Nil(err) + require.False(signature.IsZero()) + + extra := uuid.FromStringOrNil(rid).Bytes() + extra = append(extra, signature[:]...) + out := testBuildObserverRequest(node, approveRequestId, holder, common.ActionSolanaSafeApproveAccount, extra, common.CurveEdwards25519Default) + testStep(ctx, require, node, out) + + safe, err := node.store.ReadSafe(ctx, holder) + require.Nil(err) + require.Equal(SafeStateApproved, int(safe.State)) + require.Equal(approveRequestId, safe.RequestId) + require.Equal(holder, safe.Holder) + require.Equal(sp.Holder, safe.Holder) + require.Equal(sp.Signer, safe.Signer) + require.Equal(sp.Observer, safe.Observer) + require.Equal(safeAddress, safe.Address) + require.Equal(byte(1), safe.Threshold) + require.Len(safe.Receivers, 1) + require.Equal(testSafeBondReceiverId, safe.Receivers[0]) +} + +func testSolanaUpdateNetworkStatus(ctx context.Context, require *require.Assertions, node *Node, blockHeight int, blockHash string) { + id := uuid.Must(uuid.NewV4()).String() + fee, height := 0, uint64(blockHeight) + hash, err := sg.HashFromBase58(blockHash) + require.Nil(err) + + extra := []byte{common.SafeChainSolana} + extra = binary.BigEndian.AppendUint64(extra, uint64(fee)) + extra = binary.BigEndian.AppendUint64(extra, height) + extra = append(extra, hash[:]...) + dummy := hex.EncodeToString(testSolanaKeyDummyHolder.PublicKey().Bytes()) + out := testBuildObserverRequest(node, id, dummy, common.ActionObserverUpdateNetworkStatus, extra, common.CurveEdwards25519Default) + testStep(ctx, require, node, out) +} diff --git a/keeper/store/network.go b/keeper/store/network.go index a593c031..ed2675aa 100644 --- a/keeper/store/network.go +++ b/keeper/store/network.go @@ -12,7 +12,7 @@ import ( ) type Asset struct { - AssetId string + AssetId string // safe asset id MixinId string AssetKey string Symbol string diff --git a/keeper/store/request_transactions.go b/keeper/store/request_transactions.go index b6f23958..e0f130e9 100644 --- a/keeper/store/request_transactions.go +++ b/keeper/store/request_transactions.go @@ -34,7 +34,7 @@ func (s *SQLite3Store) FailAction(ctx context.Context, req *common.Request) erro err = s.writeActionResult(ctx, tx, req.Output.OutputId, "", nil, req.Id) if err != nil { return err - } + } return tx.Commit() } diff --git a/keeper/store/schema.sql b/keeper/store/schema.sql index 1828ac19..ee7042fb 100644 --- a/keeper/store/schema.sql +++ b/keeper/store/schema.sql @@ -1,285 +1,246 @@ CREATE TABLE IF NOT EXISTS requests ( - request_id VARCHAR NOT NULL, - mixin_hash VARCHAR NOT NULL, + request_id VARCHAR NOT NULL, + mixin_hash VARCHAR NOT NULL, mixin_index INTEGER NOT NULL, - asset_id VARCHAR NOT NULL, - amount VARCHAR NOT NULL, - role INTEGER NOT NULL, - action INTEGER NOT NULL, - curve INTEGER NOT NULL, - holder VARCHAR NOT NULL, - extra VARCHAR NOT NULL, - state INTEGER NOT NULL, - created_at TIMESTAMP NOT NULL, - updated_at TIMESTAMP NOT NULL, - sequence INTEGER NOT NULL, + asset_id VARCHAR NOT NULL, + amount VARCHAR NOT NULL, + role INTEGER NOT NULL, + action INTEGER NOT NULL, + curve INTEGER NOT NULL, + holder VARCHAR NOT NULL, + extra VARCHAR NOT NULL, + state INTEGER NOT NULL, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, + sequence INTEGER NOT NULL, PRIMARY KEY ('request_id') ); CREATE UNIQUE INDEX IF NOT EXISTS requests_by_mixin_hash_index ON requests(mixin_hash, mixin_index); -CREATE INDEX IF NOT EXISTS requests_by_state_created ON requests(state, created_at); - - +CREATE INDEX IF NOT EXISTS requests_by_state_created ON requests(state, created_at); CREATE TABLE IF NOT EXISTS action_results ( - output_id VARCHAR NOT NULL, - compaction VARCHAR NOT NULL, - transactions TEXT NOT NULL, - request_id VARCHAR NOT NULL, - created_at TIMESTAMP NOT NULL, + output_id VARCHAR NOT NULL, + compaction VARCHAR NOT NULL, + transactions TEXT NOT NULL, + request_id VARCHAR NOT NULL, + created_at TIMESTAMP NOT NULL, PRIMARY KEY ('output_id') ); CREATE INDEX IF NOT EXISTS action_results_by_request ON action_results(request_id); - - - CREATE TABLE IF NOT EXISTS network_infos ( - request_id VARCHAR NOT NULL, - chain INTEGER NOT NULL, - fee INTEGER NOT NULL, - hash VARCHAR NOT NULL, - height INTEGER NOT NULL, - created_at TIMESTAMP NOT NULL, + request_id VARCHAR NOT NULL, + chain INTEGER NOT NULL, + fee INTEGER NOT NULL, + hash VARCHAR NOT NULL, + height INTEGER NOT NULL, + created_at TIMESTAMP NOT NULL, PRIMARY KEY ('request_id') ); CREATE INDEX IF NOT EXISTS network_infos_by_chain_created ON network_infos(chain, created_at); - - - CREATE TABLE IF NOT EXISTS operation_params ( - request_id VARCHAR NOT NULL, - chain INTEGER NOT NULL, - price_asset VARCHAR NOT NULL, - price_amount VARCHAR NOT NULL, - transaction_minimum VARCHAR NOT NULL, - created_at TIMESTAMP NOT NULL, + request_id VARCHAR NOT NULL, + chain INTEGER NOT NULL, + price_asset VARCHAR NOT NULL, + price_amount VARCHAR NOT NULL, + transaction_minimum VARCHAR NOT NULL, + created_at TIMESTAMP NOT NULL, PRIMARY KEY ('request_id') ); CREATE INDEX IF NOT EXISTS operation_params_by_chain_created ON operation_params(chain, created_at); - - - CREATE TABLE IF NOT EXISTS assets ( - asset_id VARCHAR NOT NULL, - mixin_id VARCHAR NOT NULL, - asset_key VARCHAR NOT NULL, - symbol VARCHAR NOT NULL, - name VARCHAR NOT NULL, - decimals INTEGER NOT NULL, - chain INTEGER NOT NULL, - created_at TIMESTAMP NOT NULL, + asset_id VARCHAR NOT NULL, + mixin_id VARCHAR NOT NULL, + asset_key VARCHAR NOT NULL, + symbol VARCHAR NOT NULL, + name VARCHAR NOT NULL, + decimals INTEGER NOT NULL, + chain INTEGER NOT NULL, + created_at TIMESTAMP NOT NULL, PRIMARY KEY ('asset_id') ); CREATE UNIQUE INDEX IF NOT EXISTS assets_by_mixin_id ON assets(mixin_id); - - - - CREATE TABLE IF NOT EXISTS keys ( - public_key VARCHAR NOT NULL, - curve INTEGER NOT NULL, - request_id VARCHAR NOT NULL, - role INTEGER NOT NULL, - extra VARCHAR NOT NULL, - flags INTEGER NOT NULL, - holder VARCHAR, - created_at TIMESTAMP NOT NULL, - updated_at TIMESTAMP NOT NULL, + public_key VARCHAR NOT NULL, + curve INTEGER NOT NULL, + request_id VARCHAR NOT NULL, + role INTEGER NOT NULL, + extra VARCHAR NOT NULL, + flags INTEGER NOT NULL, + holder VARCHAR, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, PRIMARY KEY ('public_key') ); CREATE UNIQUE INDEX IF NOT EXISTS keys_by_request_id ON keys(request_id); -CREATE UNIQUE INDEX IF NOT EXISTS keys_by_holder_role ON keys(holder, role); - - - - +CREATE UNIQUE INDEX IF NOT EXISTS keys_by_holder_role ON keys(holder, role); CREATE TABLE IF NOT EXISTS safe_proposals ( - request_id VARCHAR NOT NULL, - chain INTEGER NOT NULL, - holder VARCHAR NOT NULL, - signer VARCHAR NOT NULL, - observer VARCHAR NOT NULL, - timelock INTEGER NOT NULL, - path VARCHAR NOT NULL, - address VARCHAR NOT NULL, - extra VARCHAR NOT NULL, - receivers VARCHAR NOT NULL, - threshold INTEGER NOT NULL, - created_at TIMESTAMP NOT NULL, - updated_at TIMESTAMP NOT NULL, + request_id VARCHAR NOT NULL, + chain INTEGER NOT NULL, + holder VARCHAR NOT NULL, + signer VARCHAR NOT NULL, + observer VARCHAR NOT NULL, + timelock INTEGER NOT NULL, + path VARCHAR NOT NULL, + address VARCHAR NOT NULL, + extra VARCHAR NOT NULL, + receivers VARCHAR NOT NULL, + threshold INTEGER NOT NULL, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, PRIMARY KEY ('request_id') ); CREATE UNIQUE INDEX IF NOT EXISTS safe_proposals_by_signer ON safe_proposals(signer); -CREATE UNIQUE INDEX IF NOT EXISTS safe_proposals_by_observer ON safe_proposals(observer); -CREATE UNIQUE INDEX IF NOT EXISTS safe_proposals_by_address ON safe_proposals(address); - - - +CREATE UNIQUE INDEX IF NOT EXISTS safe_proposals_by_observer ON safe_proposals(observer); +CREATE UNIQUE INDEX IF NOT EXISTS safe_proposals_by_address ON safe_proposals(address); CREATE TABLE IF NOT EXISTS safes ( - holder VARCHAR NOT NULL, - chain INTEGER NOT NULL, - signer VARCHAR NOT NULL, - observer VARCHAR NOT NULL, - timelock INTEGER NOT NULL, - path VARCHAR NOT NULL, - address VARCHAR NOT NULL, - extra VARCHAR NOT NULL, - receivers VARCHAR NOT NULL, - threshold INTEGER NOT NULL, - request_id VARCHAR NOT NULL, - nonce INTEGER NOT NULL, - state INTEGER NOT NULL, - safe_asset_id VARCHAR NOT NULL, - created_at TIMESTAMP NOT NULL, - updated_at TIMESTAMP NOT NULL, + holder VARCHAR NOT NULL, + chain INTEGER NOT NULL, + signer VARCHAR NOT NULL, + observer VARCHAR NOT NULL, + timelock INTEGER NOT NULL, + path VARCHAR NOT NULL, + address VARCHAR NOT NULL, + extra VARCHAR NOT NULL, + receivers VARCHAR NOT NULL, + threshold INTEGER NOT NULL, + request_id VARCHAR NOT NULL, + nonce INTEGER NOT NULL, + state INTEGER NOT NULL, + safe_asset_id VARCHAR NOT NULL, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, PRIMARY KEY ('holder') ); CREATE UNIQUE INDEX IF NOT EXISTS safes_by_signer ON safes(signer); -CREATE UNIQUE INDEX IF NOT EXISTS safes_by_observer ON safes(observer); -CREATE UNIQUE INDEX IF NOT EXISTS safes_by_address ON safes(address); -CREATE UNIQUE INDEX IF NOT EXISTS safes_by_request_id ON safes(request_id); --- CREATE UNIQUE INDEX IF NOT EXISTS safes_by_safe_asset_id ON safes(safe_asset_id) WHERE safe_asset_id IS NOT NULL; +CREATE UNIQUE INDEX IF NOT EXISTS safes_by_observer ON safes(observer); +CREATE UNIQUE INDEX IF NOT EXISTS safes_by_address ON safes(address); +CREATE UNIQUE INDEX IF NOT EXISTS safes_by_request_id ON safes(request_id); +-- CREATE UNIQUE INDEX IF NOT EXISTS safes_by_safe_asset_id ON safes(safe_asset_id) WHERE safe_asset_id IS NOT NULL; CREATE TABLE IF NOT EXISTS bitcoin_outputs ( - transaction_hash VARCHAR NOT NULL, - output_index INTEGER NOT NULL, - address VARCHAR NOT NULL, - satoshi INTEGER NOT NULL, - script VARCHAR NOT NULL, - sequence INTEGER NOT NULL, - chain INTEGER NOT NULL, - state INTEGER NOT NULL, - spent_by VARCHAR, - request_id VARCHAR NOT NULL, - created_at TIMESTAMP NOT NULL, - updated_at TIMESTAMP NOT NULL, + transaction_hash VARCHAR NOT NULL, + output_index INTEGER NOT NULL, + address VARCHAR NOT NULL, + satoshi INTEGER NOT NULL, + script VARCHAR NOT NULL, + sequence INTEGER NOT NULL, + chain INTEGER NOT NULL, + state INTEGER NOT NULL, + spent_by VARCHAR, + request_id VARCHAR NOT NULL, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, PRIMARY KEY ('transaction_hash', 'output_index') ); CREATE UNIQUE INDEX IF NOT EXISTS bitcoin_outputs_by_request_id ON bitcoin_outputs(request_id); -CREATE INDEX IF NOT EXISTS bitcoin_outputs_by_address_state_created ON bitcoin_outputs(address, state, created_at); - - - - +CREATE INDEX IF NOT EXISTS bitcoin_outputs_by_address_state_created ON bitcoin_outputs(address, state, created_at); CREATE TABLE IF NOT EXISTS ethereum_balances ( - address VARCHAR NOT NULL, - asset_id VARCHAR NOT NULL, - asset_address VARCHAR NOT NULL, - safe_asset_id VARCHAR NOT NULL, - balance VARCHAR NOT NULL, - latest_tx_hash VARCHAR NOT NULL, - updated_at TIMESTAMP NOT NULL, + address VARCHAR NOT NULL, + asset_id VARCHAR NOT NULL, + asset_address VARCHAR NOT NULL, + safe_asset_id VARCHAR NOT NULL, + balance VARCHAR NOT NULL, + latest_tx_hash VARCHAR NOT NULL, + updated_at TIMESTAMP NOT NULL, PRIMARY KEY ('address', 'asset_id') ); - - - - - +CREATE TABLE IF NOT EXISTS solana_balances ( + address VARCHAR NOT NULL, + asset_id VARCHAR NOT NULL, + asset_address VARCHAR NOT NULL, + safe_asset_id VARCHAR NOT NULL, + balance VARCHAR NOT NULL, + latest_tx_hash VARCHAR NOT NULL, + updated_at TIMESTAMP NOT NULL, + PRIMARY KEY ('address', 'asset_id') +); CREATE TABLE IF NOT EXISTS deposits ( - transaction_hash VARCHAR NOT NULL, - output_index VARCHAR NOT NULL, - asset_id VARCHAR NOT NULL, - amount VARCHAR NOT NULL, - receiver VARCHAR NOT NULL, - sender VARCHAR NOT NULL, - state INTEGER NOT NULL, - chain INTEGER NOT NULL, - holder VARCHAR NOT NULL, - category INTEGER NOT NULL, - created_at TIMESTAMP NOT NULL, - updated_at TIMESTAMP NOT NULL, + transaction_hash VARCHAR NOT NULL, + output_index VARCHAR NOT NULL, + asset_id VARCHAR NOT NULL, + amount VARCHAR NOT NULL, + receiver VARCHAR NOT NULL, + sender VARCHAR NOT NULL, + state INTEGER NOT NULL, + chain INTEGER NOT NULL, + holder VARCHAR NOT NULL, + category INTEGER NOT NULL, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, PRIMARY KEY ('transaction_hash', 'output_index') ); - - - - - - CREATE TABLE IF NOT EXISTS transactions ( - transaction_hash VARCHAR NOT NULL, - raw_transaction VARCHAR NOT NULL, - holder VARCHAR NOT NULL, - chain INTEGER NOT NULL, - asset_id VARCHAR NOT NULL, - state INTEGER NOT NULL, - data VARCHAR NOT NULL, - request_id VARCHAR NOT NULL, - created_at TIMESTAMP NOT NULL, - updated_at TIMESTAMP NOT NULL, + transaction_hash VARCHAR NOT NULL, + raw_transaction VARCHAR NOT NULL, + holder VARCHAR NOT NULL, + chain INTEGER NOT NULL, + asset_id VARCHAR NOT NULL, + state INTEGER NOT NULL, + data VARCHAR NOT NULL, + request_id VARCHAR NOT NULL, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, PRIMARY KEY ('transaction_hash') ); CREATE UNIQUE INDEX IF NOT EXISTS transactions_by_request_id ON transactions(request_id); - - - - CREATE TABLE IF NOT EXISTS signature_requests ( - request_id VARCHAR NOT NULL, - transaction_hash VARCHAR NOT NULL, - input_index INTEGER NOT NULL, - signer VARCHAR NOT NULL, - curve INTEGER NOT NULL, - message VARCHAR NOT NULL, - signature VARCHAR, - state INTEGER NOT NULL, - created_at TIMESTAMP NOT NULL, - updated_at TIMESTAMP NOT NULL, + request_id VARCHAR NOT NULL, + transaction_hash VARCHAR NOT NULL, + input_index INTEGER NOT NULL, + signer VARCHAR NOT NULL, + curve INTEGER NOT NULL, + message VARCHAR NOT NULL, + signature VARCHAR, + state INTEGER NOT NULL, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, PRIMARY KEY ('request_id') ); CREATE INDEX IF NOT EXISTS signature_requests_by_transaction_state_created ON signature_requests(transaction_hash, state, created_at); - - - - CREATE TABLE IF NOT EXISTS migrate_assets ( - safe_asset_id VARCHAR NOT NULL, - chain INTEGER NOT NULL, - address VARCHAR NOT NULL, - asset_id VARCHAR NOT NULL, + safe_asset_id VARCHAR NOT NULL, + chain INTEGER NOT NULL, + address VARCHAR NOT NULL, + asset_id VARCHAR NOT NULL, PRIMARY KEY ('safe_asset_id') ); CREATE UNIQUE INDEX IF NOT EXISTS migrate_assets_by_address_asset ON migrate_assets(address, asset_id); - - - - CREATE TABLE IF NOT EXISTS properties ( - key VARCHAR NOT NULL, - value VARCHAR NOT NULL, - created_at TIMESTAMP NOT NULL, + key VARCHAR NOT NULL, + value VARCHAR NOT NULL, + created_at TIMESTAMP NOT NULL, PRIMARY KEY ('key') ); diff --git a/keeper/store/signature.go b/keeper/store/signature.go index 56b14944..80f3bdf4 100644 --- a/keeper/store/signature.go +++ b/keeper/store/signature.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/MixinNetwork/safe/apps/solana" "github.com/MixinNetwork/safe/common" "github.com/MixinNetwork/trusted-group/mtg" ) @@ -177,9 +178,15 @@ func (s *SQLite3Store) FinishTransactionSignaturesWithRequest(ctx context.Contex return fmt.Errorf("UPDATE bitcoin_outputs %v", err) } } + if transactionHasBalance(safe.Chain) { for _, sb := range bm { - err = s.createOrUpdateEthereumBalance(ctx, tx, sb) + switch safe.Chain { + case solana.ChainSolana: + err = s.createOrUpdateSolanaBalance(ctx, tx, sb) + default: + err = s.createOrUpdateEthereumBalance(ctx, tx, sb) + } if err != nil { return err } diff --git a/keeper/store/solana.go b/keeper/store/solana.go new file mode 100644 index 00000000..81440fd6 --- /dev/null +++ b/keeper/store/solana.go @@ -0,0 +1,138 @@ +package store + +import ( + "context" + "database/sql" + "fmt" + "math/big" + "time" + + "github.com/MixinNetwork/safe/common" + "github.com/MixinNetwork/trusted-group/mtg" +) + +func (s *SQLite3Store) CreateSolanaBalanceDepositFromRequest(ctx context.Context, safe *Safe, sb *SafeBalance, txHash string, index int64, amount *big.Int, sender string, req *common.Request, txs []*mtg.Transaction) error { + s.mutex.Lock() + defer s.mutex.Unlock() + + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer tx.Rollback() + + err = s.createOrUpdateSolanaBalance(ctx, tx, sb) + if err != nil { + return err + } + + vals := []any{txHash, index, sb.AssetId, amount.String(), sb.Address, sender, common.RequestStateDone, safe.Chain, safe.Holder, common.ActionObserverHolderDeposit, req.CreatedAt, req.CreatedAt} + err = s.execOne(ctx, tx, buildInsertionSQL("deposits", depositsCols), vals...) + if err != nil { + return fmt.Errorf("INSERT deposits %v", err) + } + + err = s.execOne(ctx, tx, "UPDATE requests SET state=?, updated_at=? WHERE request_id=?", common.RequestStateDone, time.Now().UTC(), req.Id) + if err != nil { + return fmt.Errorf("UPDATE requests %v", err) + } + + err = s.writeActionResult(ctx, tx, req.Output.OutputId, "", txs, req.Id) + if err != nil { + return err + } + return tx.Commit() +} + +func (s *SQLite3Store) createOrUpdateSolanaBalance(ctx context.Context, tx *sql.Tx, sb *SafeBalance) error { + existed, err := s.checkExistence(ctx, tx, "SELECT balance FROM solana_balances WHERE address=? AND asset_id=?", sb.Address, sb.AssetId) + if err != nil { + return err + } else if !existed { + cols := []string{"address", "asset_id", "asset_address", "safe_asset_id", "balance", "latest_tx_hash", "updated_at"} + vals := []any{sb.Address, sb.AssetId, sb.AssetAddress, sb.SafeAssetId, sb.balance, "", time.Now().UTC()} + err = s.execOne(ctx, tx, buildInsertionSQL("solana_balances", cols), vals...) + if err != nil { + return fmt.Errorf("INSERT solana_balances %v", err) + } + } else { + err = s.execOne(ctx, tx, "UPDATE solana_balances SET balance=?, updated_at=? WHERE address=? AND asset_id=? AND safe_asset_id=?", + sb.balance, time.Now().UTC(), sb.Address, sb.AssetId, sb.SafeAssetId) + if err != nil { + return fmt.Errorf("UPDATE solana_balances %v", err) + } + } + + return nil +} + +func (s *SQLite3Store) ReadSolanaBalance(ctx context.Context, address, assetId, safeAssetId string) (*SafeBalance, error) { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer tx.Rollback() + + query := "SELECT address,asset_id,asset_address,safe_asset_id,balance,latest_tx_hash,updated_at FROM solana_balances WHERE address=? AND asset_id=?" + row := tx.QueryRowContext(ctx, query, address, assetId) + + var sb SafeBalance + err = row.Scan(&sb.Address, &sb.AssetId, &sb.AssetAddress, &sb.SafeAssetId, &sb.balance, &sb.LatestTxHash, &sb.UpdatedAt) + if err == sql.ErrNoRows { + return &SafeBalance{ + Address: address, + AssetId: assetId, + SafeAssetId: safeAssetId, + balance: "0", + LatestTxHash: "", + UpdatedAt: time.Now().UTC(), + }, nil + } else if err != nil { + return nil, err + } + if sb.SafeAssetId != safeAssetId { + panic(sb.AssetId) + } + return &sb, nil +} + +func (s *SQLite3Store) ReadAllSolanaTokenBalances(ctx context.Context, address string) ([]*SafeBalance, error) { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer tx.Rollback() + + query := "SELECT address,asset_id,asset_address,safe_asset_id,balance,latest_tx_hash,updated_at FROM solana_balances WHERE address=?" + rows, err := s.db.QueryContext(ctx, query, address) + if err != nil { + return nil, err + } + defer rows.Close() + + var sbs []*SafeBalance + for rows.Next() { + var b SafeBalance + err = rows.Scan(&b.Address, &b.AssetId, &b.AssetAddress, &b.SafeAssetId, &b.balance, &b.LatestTxHash, &b.UpdatedAt) + if err != nil { + return nil, err + } + if b.SafeAssetId == "" { + panic(b.AssetId) + } + sbs = append(sbs, &b) + } + return sbs, nil +} + +func (s *SQLite3Store) ReadAllSolanaTokenBalancesMap(ctx context.Context, address string) (map[string]*SafeBalance, error) { + sbs, err := s.ReadAllSolanaTokenBalances(ctx, address) + if err != nil { + return nil, err + } + sbm := make(map[string]*SafeBalance, len(sbs)) + for _, sb := range sbs { + sbm[sb.AssetAddress] = sb + } + return sbm, nil +} diff --git a/keeper/store/transaction.go b/keeper/store/transaction.go index c00c624a..b4bf87b1 100644 --- a/keeper/store/transaction.go +++ b/keeper/store/transaction.go @@ -9,6 +9,7 @@ import ( "github.com/MixinNetwork/safe/apps/bitcoin" "github.com/MixinNetwork/safe/apps/ethereum" + "github.com/MixinNetwork/safe/apps/solana" "github.com/MixinNetwork/safe/common" "github.com/MixinNetwork/trusted-group/mtg" ) @@ -348,6 +349,8 @@ func transactionHasBalance(chain byte) bool { return false case ethereum.ChainEthereum, ethereum.ChainPolygon: return true + case solana.ChainSolana: + return true default: panic(chain) } diff --git a/observer/bitcoin.go b/observer/bitcoin.go index b2c13392..0b7c531f 100644 --- a/observer/bitcoin.go +++ b/observer/bitcoin.go @@ -358,11 +358,16 @@ func (node *Node) bitcoinRPCBlocksLoop(ctx context.Context, chain byte) { func (node *Node) bitcoinProcessTransaction(ctx context.Context, tx *bitcoin.RPCTransaction, chain byte) error { for index := range tx.Vout { + /// out 是输出 out := tx.Vout[index] + /// skt 是脚本类型 skt := out.ScriptPubKey.Type + /// 如果脚本类型不是 witness script hash 或 witness key hash, 则跳过 if skt != bitcoin.ScriptPubKeyTypeWitnessScriptHash && skt != bitcoin.ScriptPubKeyTypeWitnessKeyHash { continue } + + /// 如果输出索引不等于 index, 则 panic if out.N != int64(index) { panic(tx.TxId) } diff --git a/observer/holder.go b/observer/holder.go index 3a3ec415..7e6e4fac 100644 --- a/observer/holder.go +++ b/observer/holder.go @@ -17,6 +17,7 @@ import ( "github.com/MixinNetwork/safe/apps/ethereum" "github.com/MixinNetwork/safe/common" gc "github.com/ethereum/go-ethereum/common" + sg "github.com/gagliardetto/solana-go" "github.com/gofrs/uuid/v5" ) @@ -91,6 +92,9 @@ func (node *Node) keeperSaveTransactionProposal(ctx context.Context, chain byte, case common.SafeChainEthereum, common.SafeChainPolygon: t, _ := ethereum.UnmarshalSafeTransaction(extra) txHash = t.TxHash + case common.SafeChainSolana: + t, _ := sg.TransactionFromBytes(extra) + txHash = t.Message.RecentBlockhash.String() } tx, err := node.keeperStore.ReadTransaction(ctx, txHash) if err != nil { diff --git a/observer/interface.go b/observer/interface.go index cf1279fa..177e4f08 100644 --- a/observer/interface.go +++ b/observer/interface.go @@ -25,6 +25,9 @@ type Configuration struct { BitcoinRPC string `toml:"bitcoin-rpc"` LitecoinRPC string `toml:"litecoin-rpc"` EthereumRPC string `toml:"ethereum-rpc"` + SolanaRPC string `toml:"solana-rpc"` + SolanaWS string `toml:"solana-ws"` + SolanaPayer string `toml:"solana-payer"` PolygonRPC string `toml:"polygon-rpc"` PolygonFactoryAddress string `toml:"polygon-factory-address"` PolygonObserverDepositEntry string `toml:"polygon-observer-deposit-entry"` diff --git a/observer/key.go b/observer/key.go index bf4cefe0..25365715 100644 --- a/observer/key.go +++ b/observer/key.go @@ -32,6 +32,9 @@ func (node *Node) safeAddObserverKeys(ctx context.Context, chain byte) error { case common.SafeChainEthereum: crv = common.CurveSecp256k1ECDSAEthereum } + + /// 获取观察者密钥数量, 如果数量小于1000, 则继续添加观察者密钥 + /// spare keys 就是没有被使用的密钥 count, err := node.keeperStore.CountSpareKeys(ctx, crv, common.RequestFlagNone, common.RequestRoleObserver) if err != nil { return err diff --git a/observer/node.go b/observer/node.go index 5837abb6..f78feb70 100644 --- a/observer/node.go +++ b/observer/node.go @@ -68,7 +68,9 @@ func (node *Node) Boot(ctx context.Context) { common.SafeChainLitecoin, common.SafeChainPolygon, common.SafeChainEthereum, + common.SafeChainSolana, } { + // send price info for each chain on observer boot up (temporary) err := node.sendPriceInfo(ctx, chain) if err != nil { panic(err) @@ -87,26 +89,42 @@ func (node *Node) Boot(ctx context.Context) { go node.ethereumDepositConfirmLoop(ctx, chain) go node.ethereumTransactionApprovalLoop(ctx, chain) go node.ethereumTransactionSpendLoop(ctx, chain) + case common.SafeChainSolana: + go node.solanaNetworkInfoLoop(ctx) + go node.solanaRPCBlocksLoop(ctx) + go node.solanaDepositConfirmLoop(ctx) + go node.solanaTransactionApprovalLoop(ctx) + go node.solanaTransactionSpentLoop(ctx) } } + go node.safeKeyLoop(ctx, common.SafeChainBitcoin) go node.safeKeyLoop(ctx, common.SafeChainEthereum) + go node.mixinWithdrawalsLoop(ctx) + go node.sendAccountApprovals(ctx) + go node.Blaze(ctx) node.snapshotsLoop(ctx) } func (node *Node) sendPriceInfo(ctx context.Context, chain byte) error { + // assetID is chain id of the asset var assetId string switch chain { case common.SafeChainBitcoin, common.SafeChainLitecoin: _, assetId = node.bitcoinParams(chain) case common.SafeChainPolygon, common.SafeChainEthereum: _, assetId = node.ethereumParams(chain) + case common.SafeChainSolana: + assetId = common.SafeSolanaChainId default: panic(chain) } + + /// asset is the asset meta of the operation price asset + /// 也就是 gas asset asset, err := node.fetchAssetMeta(ctx, node.conf.OperationPriceAssetId) if err != nil { return err @@ -127,14 +145,14 @@ func (node *Node) sendPriceInfo(ctx context.Context, chain byte) error { } dummy := node.bitcoinDummyHolder() id := common.UniqueId("ActionObserverSetOperationParams", dummy) - id = common.UniqueId(id, assetId) - id = common.UniqueId(id, asset.AssetId) + id = common.UniqueId(id, assetId) // chain asset id + id = common.UniqueId(id, asset.AssetId) // gas asset id id = common.UniqueId(id, amount.String()) id = common.UniqueId(id, minimum.String()) extra := []byte{chain} - extra = append(extra, uuid.Must(uuid.FromString(asset.AssetId)).Bytes()...) - extra = binary.BigEndian.AppendUint64(extra, uint64(amount.IntPart())) - extra = binary.BigEndian.AppendUint64(extra, uint64(minimum.IntPart())) + extra = append(extra, uuid.Must(uuid.FromString(asset.AssetId)).Bytes()...) // append gas asset id + extra = binary.BigEndian.AppendUint64(extra, uint64(amount.IntPart())) // append gas amount + extra = binary.BigEndian.AppendUint64(extra, uint64(minimum.IntPart())) // append gas minimum return node.sendKeeperResponse(ctx, dummy, common.ActionObserverSetOperationParams, chain, id, extra) } @@ -159,6 +177,7 @@ func (node *Node) sendAccountApprovals(ctx context.Context) { continue } + /// sp is 安全提案 sp, err := node.keeperStore.ReadSafeProposalByAddress(ctx, account.Address) if err != nil { panic(err) @@ -473,7 +492,7 @@ func (node *Node) handleKeeperResponse(ctx context.Context, s *mixin.SafeSnapsho } switch op.Type { - case common.ActionBitcoinSafeProposeTransaction, common.ActionEthereumSafeProposeTransaction: + case common.ActionBitcoinSafeProposeTransaction, common.ActionEthereumSafeProposeTransaction, common.ActionSolanaSafeProposeTransaction: return true, node.keeperSaveTransactionProposal(ctx, chain, data, s.CreatedAt) case common.ActionBitcoinSafeApproveTransaction: return true, node.keeperCombineBitcoinTransactionSignatures(ctx, data) @@ -557,6 +576,8 @@ func depositCheckpointDefault(chain byte) int64 { return 52950000 case common.SafeChainEthereum: return 19175473 + case common.SafeChainSolana: + return 0 default: panic(chain) } @@ -568,6 +589,8 @@ func depositCheckpointKey(chain byte) string { return fmt.Sprintf("bitcoin-deposit-checkpoint-%d", chain) case common.SafeChainEthereum, common.SafeChainPolygon: return fmt.Sprintf("ethereum-deposit-checkpoint-%d", chain) + case common.SafeChainSolana: + return fmt.Sprintf("solana-deposit-checkpoint-%d", chain) default: panic(chain) } @@ -591,6 +614,8 @@ func (node *Node) getChainFinalizationDelay(chain byte) int64 { return 32 case common.SafeChainPolygon: return 512 + case common.SafeChainSolana: + return 32 default: panic(chain) } diff --git a/observer/solana.go b/observer/solana.go new file mode 100644 index 00000000..28c8ac18 --- /dev/null +++ b/observer/solana.go @@ -0,0 +1,576 @@ +package observer + +import ( + "context" + "encoding/base64" + "encoding/binary" + "fmt" + "math/big" + "time" + + "github.com/MixinNetwork/mixin/crypto" + "github.com/MixinNetwork/mixin/logger" + "github.com/MixinNetwork/safe/apps/ethereum" + "github.com/MixinNetwork/safe/apps/solana" + "github.com/MixinNetwork/safe/common" + "github.com/MixinNetwork/safe/keeper" + sg "github.com/gagliardetto/solana-go" + "github.com/gofrs/uuid/v5" + "github.com/shopspring/decimal" +) + +func (node *Node) solanaClient() *solana.Client { + rpc := node.conf.SolanaRPC + ws := node.conf.SolanaWS + return solana.NewClient(rpc, ws) +} + +func (node *Node) solanaNetworkInfoLoop(ctx context.Context) { + client := node.solanaClient() + chain := byte(common.SafeChainSolana) + + for { + time.Sleep(depositNetworkInfoDelay) + + height, blockHash, err := client.RPCGetBlockHeight(ctx) + if err != nil { + logger.Printf("solana.RPCGetBlockHeight => %v", err) + continue + } + + delay := node.getChainFinalizationDelay(chain) + if delay > height || delay < 1 { + panic(delay) + } + height = int64(height) + 1 - delay + info, err := node.keeperStore.ReadLatestNetworkInfo(ctx, chain, time.Now()) + if err != nil { + panic(err) + } + if info != nil && info.Height > uint64(height) { + logger.Printf("node.keeperStore.ReadLatestNetworkInfo(%d) => %v %d", chain, info, height) + continue + } + + unitPrice, err := client.RPCGetUnitPrice(ctx) + if err != nil { + logger.Printf("solana.RPCGetUnitPrice => %v", err) + continue + } + + extra := []byte{chain} + extra = binary.BigEndian.AppendUint64(extra, uint64(height)) + extra = append(extra, blockHash[:]...) + id := common.UniqueId(common.SafeSolanaChainId, fmt.Sprintf("%s:%d", blockHash, height)) + id = common.UniqueId(id, fmt.Sprintf("%d:%d", time.Now().UnixNano(), unitPrice)) + logger.Printf("node.ethereumNetworkInfoLoop(%d) => %d %d %s %s", chain, height, unitPrice, blockHash, id) + + dummy := node.bitcoinDummyHolder() + action := common.ActionObserverUpdateNetworkStatus + err = node.sendKeeperResponse(ctx, dummy, byte(action), chain, id, extra) + logger.Verbosef("node.sendKeeperResponse(%d, %s, %x) => %v", chain, id, extra, err) + } +} + +func (node *Node) solanaRPCBlocksLoop(ctx context.Context) { + client := node.solanaClient() + + for { + checkpoint, err := node.readDepositCheckpoint(ctx, common.SafeChainSolana) + if err != nil { + panic(err) + } + height, _, err := client.RPCGetBlockHeight(ctx) + if err != nil { + logger.Printf("solana.RPCGetBlockHeight => %v", err) + time.Sleep(time.Second * 5) + continue + } + logger.Printf("node.solanaReadDepositCheckpoint(%d) => %d %d", common.SafeChainSolana, checkpoint, height) + delay := node.getChainFinalizationDelay(common.SafeChainSolana) + if checkpoint+delay > height+1 { + time.Sleep(time.Second * 5) + continue + } + err = node.solanaReadBlock(ctx, checkpoint) + logger.Printf("node.solanaReadBlock(%d, %d) => %v", common.SafeChainSolana, checkpoint, err) + if err != nil { + time.Sleep(time.Second * 5) + continue + } + + err = node.solanaWriteDepositCheckpoint(ctx, checkpoint+1) + if err != nil { + panic(err) + } + } + +} + +func (node *Node) solanaReadBlock(ctx context.Context, checkpoint int64) error { + client := node.solanaClient() + block, err := client.RPCGetBlockByHeight(ctx, uint64(checkpoint)) + if err != nil || block == nil { + return err + } + + for _, tx := range block.Transactions { + transfers, err := client.ExtractTransfersFromTransaction(ctx, tx.MustGetTransaction(), tx.Meta) + if err != nil { + return err + } + + if err := node.solanaProcessTransfers(ctx, *block.BlockHeight, transfers); err != nil { + return err + } + } + + return nil +} + +func (node *Node) parseSolanaBlockBalanceChanges(ctx context.Context, transfers []*solana.Transfer) (map[string]*big.Int, error) { + changes := make(map[string]*big.Int) + for _, t := range transfers { + if t.Receiver == solana.SolanaEmptyAddress { + continue + } + + safe, err := node.keeperStore.ReadSafeByAddress(ctx, t.Receiver) + logger.Verbosef("keeperStore.ReadSafeByAddress(%s) => %v %v", t.Receiver, safe, err) + if err != nil { + return nil, err + } else if safe == nil || safe.Chain != common.SafeChainSolana { + continue + } + + old, err := node.keeperStore.ReadDeposit(ctx, t.Signature, t.Index) + logger.Printf("keeperStore.ReadDeposit(%s, %d, %s, %s) => %v %v", t.Signature, t.Index, t.AssetId, t.Receiver, old, err) + if err != nil { + return nil, err + } else if old != nil { + continue + } + + key := fmt.Sprintf("%s:%s", t.Receiver, t.TokenAddress) + total := changes[key] + if total != nil { + changes[key] = new(big.Int).Add(total, t.Value) + } else { + changes[key] = t.Value + } + } + return changes, nil +} + +func (node *Node) solanaProcessTransfers(ctx context.Context, height uint64, transfers []*solana.Transfer) error { + changes, err := node.parseSolanaBlockBalanceChanges(ctx, transfers) + logger.Printf("node.parseSolanaBlockBalanceChanges(%d, %d, %d) => %d %v", common.SafeChainSolana, height, len(transfers), len(changes), err) + if err != nil || len(changes) == 0 { + return err + } + + for _, transfer := range transfers { + key := fmt.Sprintf("%s:%s", transfer.Receiver, transfer.TokenAddress) + if _, ok := changes[key]; !ok { + continue + } + err := node.solanaWritePendingDeposit(ctx, transfer) + if err != nil { + panic(err) + } + } + return nil +} + +func (node *Node) solanaWriteDepositCheckpoint(ctx context.Context, checkpoint int64) error { + return node.store.WriteProperty(ctx, depositCheckpointKey(common.SafeChainSolana), fmt.Sprint(checkpoint)) +} + +func (node *Node) solanaWritePendingDeposit(ctx context.Context, transfer *solana.Transfer) error { + old, err := node.keeperStore.ReadDeposit(ctx, transfer.Signature, transfer.Index) + logger.Printf("keeperStore.ReadDeposit(%s, %d, %s, %s) => %v %v", transfer.Signature, transfer.Index, transfer.AssetId, transfer.Receiver, old, err) + if err != nil { + return fmt.Errorf("keeperStore.ReadDeposit(%s, %d, %s, %s) => %v %v", transfer.Signature, transfer.Index, transfer.AssetId, transfer.Receiver, old, err) + } else if old != nil { + return nil + } + + safe, err := node.keeperStore.ReadSafeByAddress(ctx, transfer.Receiver) + logger.Printf("keeperStore.ReadSafeByAddress(%s) => %v %v", transfer.Receiver, safe, err) + if err != nil { + return fmt.Errorf("keeperStore.ReadSafeByAddress(%s) => %v", transfer.Receiver, err) + } else if safe == nil { + return nil + } + + asset, err := node.fetchAssetMeta(ctx, transfer.AssetId) + logger.Printf("node.fetchAssetMeta(%s) => %v %v", transfer.AssetId, asset, err) + if err != nil || asset == nil { + return err + } + + _, err = node.checkOrDeployKeeperBond(ctx, safe.Chain, transfer.AssetId, transfer.TokenAddress, safe.Holder, safe.Address) + if err != nil { + return fmt.Errorf("node.checkOrDeployKeeperBond(%s) => %v", safe.Holder, err) + } + + var amount decimal.Decimal + switch transfer.AssetId { + case common.SafeSolanaChainId: + amount = decimal.NewFromBigInt(transfer.Value, -int32(solana.NativeTokenDecimals)) + default: + client := node.solanaClient() + asset, err := client.RPCGetAsset(ctx, transfer.TokenAddress) + if err != nil { + return err + } + amount = decimal.NewFromBigInt(transfer.Value, -int32(asset.Decimals)) + } + + id := common.UniqueId(transfer.AssetId, safe.Holder) + id = common.UniqueId(id, fmt.Sprintf("%s:%d", transfer.Signature, transfer.Index)) + createdAt := time.Now().UTC() + deposit := &Deposit{ + TransactionHash: transfer.Signature, + OutputIndex: transfer.Index, + AssetId: transfer.AssetId, + AssetAddress: transfer.TokenAddress, + Amount: amount.String(), + Receiver: transfer.Receiver, + Sender: transfer.Sender, + Holder: safe.Holder, + Category: common.ActionObserverHolderDeposit, + State: common.RequestStateInitial, + Chain: common.SafeChainSolana, + RequestId: id, + CreatedAt: createdAt, + UpdatedAt: createdAt, + } + + err = node.store.WritePendingDepositIfNotExists(ctx, deposit) + if err != nil { + return fmt.Errorf("store.WritePendingDeposit(%v) => %v", deposit, err) + } + return nil +} + +func (node *Node) solanaDepositConfirmLoop(ctx context.Context) { + for { + time.Sleep(3 * time.Second) + deposits, err := node.store.ListDeposits(ctx, common.SafeChainSolana, "", common.RequestStateInitial, 0) + if err != nil { + panic(err) + } + for _, d := range deposits { + err := node.solanaConfirmPendingDeposit(ctx, d) + if err != nil { + panic(err) + } + } + } +} + +func (node *Node) solanaConfirmPendingDeposit(ctx context.Context, deposit *Deposit) error { + client := node.solanaClient() + + asset, err := node.store.ReadAssetMeta(ctx, deposit.AssetId) + if err != nil || asset == nil { + return err + } + safe, err := node.keeperStore.ReadSafe(ctx, deposit.Holder) + if err != nil || safe == nil { + return err + } + bonded, err := node.checkOrDeployKeeperBond(ctx, deposit.Chain, deposit.AssetId, asset.AssetKey, deposit.Holder, safe.Address) + if err != nil { + return fmt.Errorf("node.checkOrDeployKeeperBond(%s) => %v", deposit.Holder, err) + } else if !bonded { + return nil + } + + decimals := int32(solana.NativeTokenDecimals) + switch asset.AssetId { + case common.SafeSolanaChainId: + default: + decimals = int32(asset.Decimals) + } + + info, err := node.keeperStore.ReadLatestNetworkInfo(ctx, deposit.Chain, time.Now()) + if err != nil { + return fmt.Errorf("keeperStore.ReadLatestNetworkInfo(%d) => %v", deposit.Chain, err) + } else if info == nil { + return nil + } + if info.CreatedAt.After(time.Now()) { + panic(fmt.Errorf("malicious solana network info %v", info)) + } + + match, tx, err := client.VerifyDeposit(ctx, deposit.TransactionHash, deposit.AssetAddress, deposit.Receiver, deposit.OutputIndex, ethereum.ParseAmount(deposit.Amount, decimals)) + if err != nil { + panic(err) + } + if match == nil { + panic(fmt.Errorf("malicious solana deposit %s", deposit.TransactionHash)) + } + + isSafe, err := node.checkTrustedSender(ctx, deposit.Sender) + if err != nil { + return fmt.Errorf("node.checkTrustedSender(%s) => %v", deposit.Sender, err) + } + confirmations := info.Height - tx.Slot + 1 + if info.Height < tx.Slot { + confirmations = 0 + } + if isSafe && confirmations > 0 { + confirmations = 1000000 + } + if !ethereum.CheckFinalization(confirmations, deposit.Chain) { + return nil + } + + return node.sendKeeperDepositTransaction(ctx, deposit, int32(asset.Decimals)) +} + +func (node *Node) solanaTransactionApprovalLoop(ctx context.Context) { + for { + time.Sleep(3 * time.Second) + approvals, err := node.store.ListPendingTransactionApprovals(ctx, common.SafeChainSolana) + if err != nil { + panic(err) + } + + for _, approval := range approvals { + err := node.sendToKeeperSolanaApproveTransaction(ctx, approval) + logger.Verbosef("node.sendToKeeperSolanaApproveTransaction(%v) => %v", approval, err) + if err != nil { + panic(err) + } + } + } +} + +func (node *Node) sendToKeeperSolanaApproveTransaction(ctx context.Context, approval *Transaction) error { + safe, err := node.keeperStore.ReadSafe(ctx, approval.Holder) + if err != nil { + return err + } + + raw := common.DecodeHexOrPanic(approval.RawTransaction) + tx, err := sg.TransactionFromBytes(raw) + if err != nil { + panic(approval.RawTransaction) + } + + if solana.CheckTransactionSignedBy(tx, sg.MPK(safe.Observer)) { + return node.sendToKeeperSolanaApproveRecoveryTransaction(ctx, approval) + } + return node.sendToKeeperSolanaApproveNormalTransaction(ctx, approval) +} + +func (node *Node) sendToKeeperSolanaApproveRecoveryTransaction(ctx context.Context, approval *Transaction) error { + raw := common.DecodeHexOrPanic(approval.RawTransaction) + stx, err := sg.TransactionFromBytes(raw) + logger.Printf("sg.TransactionFromBytes(%s) => %v %v", approval.RawTransaction, stx, err) + if err != nil { + panic(approval.RawTransaction) + } + + safe, err := node.keeperStore.ReadSafe(ctx, approval.Holder) + logger.Printf("keeperStore.ReadSafe(%s) => %v %v", approval.Holder, safe, err) + if err != nil { + return err + } + + signedByHolder := solana.CheckTransactionSignedBy(stx, sg.MPK(safe.Holder)) + + var extra []byte + switch { + case signedByHolder: + extra = uuid.Nil.Bytes() + default: + signed, err := node.solanaCheckKeeperSignedTransaction(ctx, approval) + logger.Printf("node.solanaCheckKeeperSignedTransaction(%v) => %t %v", approval, signed, err) + if err != nil || signed { + return err + } + + tx, err := node.keeperStore.ReadTransaction(ctx, approval.TransactionHash) + if err != nil { + return err + } + extra = uuid.Must(uuid.FromString(tx.RequestId)).Bytes() + } + + objectRaw := raw + rawId := common.UniqueId(approval.RawTransaction, approval.RawTransaction) + objectRaw = append(uuid.Must(uuid.FromString(rawId)).Bytes(), objectRaw...) + objectRaw = common.AESEncrypt(node.aesKey[:], objectRaw, rawId) + msg := base64.RawURLEncoding.EncodeToString(objectRaw) + traceId := common.UniqueId(msg, msg) + ref, err := common.WriteStorageUntilSufficient(ctx, node.mixin, objectRaw, traceId, node.safeUser()) + logger.Printf("common.CreateObjectUntilSufficient(%v) => %s %v", msg, ref, err) + if err != nil { + return err + } + + destination, err := solanaExtractTransactionDestination(stx) + if err != nil { + panic(err) + } + + id := common.UniqueId(safe.Address, destination.String()) + extra = append(extra, ref[:]...) + action := common.ActionSolanaSafeCloseAccount + references := []crypto.Hash{ref} + err = node.sendKeeperResponseWithReferences(ctx, safe.Holder, byte(action), safe.Chain, id, extra, references) + logger.Printf("node.sendKeeperResponseWithReferences(%s, %s, %x, %v) => %v", safe.Holder, id, extra, references, err) + if err != nil { + return err + } + + if approval.UpdatedAt.Add(keeper.SafeSignatureTimeout).After(time.Now()) { + return nil + } + id = common.UniqueId(id, approval.UpdatedAt.String()) + err = node.sendKeeperResponseWithReferences(ctx, safe.Holder, byte(action), approval.Chain, id, extra, references) + logger.Printf("node.sendKeeperResponseWithReferences(%s, %d, %s, %x, %s)", safe.Holder, action, id, extra, ref) + if err != nil { + return err + } + return node.store.UpdateTransactionApprovalRequestTime(ctx, approval.TransactionHash) +} + +func solanaExtractTransactionDestination(tx *sg.Transaction) (sg.PublicKey, error) { + outputs := solana.ExtractOutputs(tx) + if len(outputs) == 0 { + return sg.PublicKey{}, fmt.Errorf("no outputs found") + } + + destination := outputs[0].Destination + for _, o := range outputs { + if o.Destination != destination { + return sg.PublicKey{}, fmt.Errorf("multiple destinations found") + } + } + + return sg.PublicKeyFromBase58(destination) +} + +func (node *Node) sendToKeeperSolanaApproveNormalTransaction(ctx context.Context, approval *Transaction) error { + signed, err := node.solanaCheckKeeperSignedTransaction(ctx, approval) + logger.Printf("node.solanaCheckKeeperSignedTransaction(%v) => %t %v", approval, signed, err) + if err != nil || signed { + return err + } + + raw := common.DecodeHexOrPanic(approval.RawTransaction) + stx, err := sg.TransactionFromBytes(raw) + if err != nil { + panic(approval.RawTransaction) + } + + // transaction must be signed by the holder + if !solana.CheckTransactionSignedBy(stx, sg.MPK(approval.Holder)) { + panic(approval.RawTransaction) + } + + rawId := common.UniqueId(approval.RawTransaction, approval.RawTransaction) + raw = append(uuid.Must(uuid.FromString(rawId)).Bytes(), raw...) + raw = common.AESEncrypt(node.aesKey[:], raw, rawId) + msg := base64.RawURLEncoding.EncodeToString(raw) + traceId := common.UniqueId(msg, msg) + ref, err := common.WriteStorageUntilSufficient(ctx, node.mixin, raw, traceId, node.safeUser()) + logger.Printf("WriteStorageUntilSufficient(%s) => %s %v", traceId, ref, err) + if err != nil { + return err + } + + tx, err := node.keeperStore.ReadTransaction(ctx, approval.TransactionHash) + if err != nil { + return err + } + id := common.UniqueId(approval.TransactionHash, approval.TransactionHash) + rid := uuid.Must(uuid.FromString(tx.RequestId)) + extra := append(rid.Bytes(), ref[:]...) + references := []crypto.Hash{ref} + action := common.ActionEthereumSafeApproveTransaction + err = node.sendKeeperResponseWithReferences(ctx, tx.Holder, byte(action), approval.Chain, id, extra, references) + logger.Printf("node.sendKeeperResponseWithReferences(%s, %d, %s, %x, %s)", tx.Holder, action, id, extra, ref) + if err != nil { + return err + } + + if approval.UpdatedAt.Add(keeper.SafeSignatureTimeout).After(time.Now()) { + return nil + } + id = common.UniqueId(id, approval.UpdatedAt.String()) + err = node.sendKeeperResponseWithReferences(ctx, tx.Holder, byte(action), approval.Chain, id, extra, references) + logger.Printf("node.sendKeeperResponseWithReferences(%s, %d, %s, %x, %s)", tx.Holder, action, id, extra, ref) + if err != nil { + return err + } + return node.store.UpdateTransactionApprovalRequestTime(ctx, approval.TransactionHash) +} + +func (node *Node) solanaCheckKeeperSignedTransaction(ctx context.Context, approval *Transaction) (bool, error) { + requests, err := node.keeperStore.ListAllSignaturesForTransaction(ctx, approval.TransactionHash, common.RequestStateDone) + if err != nil { + return false, err + } + if len(requests) != 1 { + return false, err + } + sig := common.DecodeHexOrPanic(requests[0].Signature.String) + if len(sig) < sg.SignatureLength { + return false, nil + } + return true, nil +} + +func (node *Node) solanaTransactionSpentLoop(ctx context.Context) { + client := node.solanaClient() + + for { + time.Sleep(3 * time.Second) + txs, err := node.store.ListFullySignedTransactionApprovals(ctx, common.SafeChainSolana) + if err != nil { + panic(err) + } + + for _, tx := range txs { + raw := common.DecodeHexOrPanic(tx.RawTransaction) + stx, err := sg.TransactionFromBytes(raw) + if err != nil { + panic(tx.RawTransaction) + } + + payer := sg.MustPrivateKeyFromBase58(node.conf.SolanaPayer) + if err := solana.Sign(stx, payer); err != nil { + panic(err) + } + + sig, err := client.SendTransaction(ctx, stx) + if err != nil { + panic(err) + } + + if err := node.store.ConfirmFullySignedTransactionApproval(ctx, tx.TransactionHash, sig, tx.RawTransaction); err != nil { + panic(err) + } + + etx, err := client.RPCGetTransaction(ctx, sig) + if err != nil || etx == nil || etx.Slot == 0 { + panic(fmt.Errorf("solana.RPCGetTransaction(%s) => %v %v", sig, etx, err)) + } + + transfers, err := client.ExtractTransfersFromTransaction(ctx, common.Must(etx.Transaction.GetTransaction()), etx.Meta) + if err != nil { + panic(err) + } + + if err := node.solanaProcessTransfers(ctx, etx.Slot, transfers); err != nil { + panic(err) + } + } + } +}