package client import ( "context" "fmt" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // StaticClient is a wrapper over Neo:Morph client // that invokes single smart contract methods with fixed fee. // // Working static client must be created via constructor NewStatic. // Using the StaticClient that has been created with new(StaticClient) // expression (or just declaring a StaticClient variable) is unsafe // and can lead to panic. type StaticClient struct { staticOpts client *Client // neo-go client instance scScriptHash util.Uint160 // contract script-hash } type staticOpts struct { tryNotary bool alpha bool // use client's key to sign notary request's main TX fee fixedn.Fixed8 } // WithNotary returns notary status of the client. // // See also TryNotary. func (s *StaticClient) WithNotary() bool { return s.client.IsNotaryEnabled() } // IsAlpha returns Alphabet status of the client. // // See also AsAlphabet. func (s *StaticClient) IsAlpha() bool { return s.alpha } // StaticClientOption allows to set an optional // parameter of StaticClient. type StaticClientOption func(*staticOpts) // NewStatic creates, initializes and returns the StaticClient instance. // // If provided Client instance is nil, ErrNilClient is returned. // // Specified fee is used by default. Per-operation fees can be customized via WithCustomFee option. func NewStatic(client *Client, scriptHash util.Uint160, fee fixedn.Fixed8, opts ...StaticClientOption) (*StaticClient, error) { if client == nil { return nil, ErrNilClient } c := &StaticClient{ client: client, scScriptHash: scriptHash, } c.fee = fee for i := range opts { opts[i](&c.staticOpts) } return c, nil } // Morph return wrapped raw morph client. func (s StaticClient) Morph() *Client { return s.client } // InvokePrm groups parameters of the Invoke operation. type InvokePrm struct { TestInvokePrm // optional parameters InvokePrmOptional } // InvokePrmOptional groups optional parameters of the Invoke operation. type InvokePrmOptional struct { // hash is an optional hash of the transaction // that generated the notification that required // to invoke notary request. // It is used to generate same but unique nonce and // `validUntilBlock` values by all notification // receivers. hash *util.Uint256 // controlTX controls whether the invoke method will use a rounded // block height value, which is useful for control transactions which // are required to be produced by all nodes with very high probability. // It's only used by notary transactions and it affects only the // computation of `validUntilBlock` values. controlTX bool // vub is used to set custom valid until block value. vub uint32 } // SetHash sets optional hash of the transaction. // If hash is set and notary is enabled, StaticClient // uses it for notary nonce and `validUntilBlock` // calculation. func (i *InvokePrmOptional) SetHash(hash util.Uint256) { i.hash = &hash } // SetControlTX sets whether a control transaction will be used. func (i *InvokePrmOptional) SetControlTX(b bool) { i.controlTX = b } // IsControl gets whether a control transaction will be used. func (i *InvokePrmOptional) IsControl() bool { return i.controlTX } // SetVUB sets valid until block value. func (i *InvokePrmOptional) SetVUB(v uint32) { i.vub = v } type InvokeRes struct { VUB uint32 } // Invoke calls Invoke method of Client with static internal script hash and fee. // Supported args types are the same as in Client. // // If TryNotary is provided: // - if AsAlphabet is provided, calls NotaryInvoke; // - otherwise, calls NotaryInvokeNotAlpha. // // If fee for the operation executed using specified method is customized, then StaticClient uses it. // Otherwise, default fee is used. func (s StaticClient) Invoke(ctx context.Context, prm InvokePrm) (InvokeRes, error) { var res InvokeRes var err error var vubP *uint32 if s.tryNotary { if s.alpha { var ( nonce uint32 = 1 vub uint32 err error ) if prm.hash != nil { if prm.controlTX { nonce, vub, err = s.client.CalculateNonceAndVUBControl(prm.hash) } else { nonce, vub, err = s.client.CalculateNonceAndVUB(prm.hash) } if err != nil { return InvokeRes{}, fmt.Errorf("could not calculate nonce and VUB for notary alphabet invoke: %w", err) } vubP = &vub } if prm.vub > 0 { vubP = &prm.vub } res.VUB, err = s.client.NotaryInvoke(ctx, s.scScriptHash, s.fee, nonce, vubP, prm.method, prm.args...) return res, err } if prm.vub > 0 { vubP = &prm.vub } res.VUB, err = s.client.NotaryInvokeNotAlpha(ctx, s.scScriptHash, s.fee, vubP, prm.method, prm.args...) return res, err } res.VUB, err = s.client.Invoke( ctx, s.scScriptHash, s.fee, prm.method, prm.args..., ) return res, err } // TestInvokePrm groups parameters of the TestInvoke operation. type TestInvokePrm struct { method string args []any } // SetMethod sets method of the contract to call. func (ti *TestInvokePrm) SetMethod(method string) { ti.method = method } // SetArgs sets arguments of the contact call. func (ti *TestInvokePrm) SetArgs(args ...any) { ti.args = args } // TestInvoke calls TestInvoke method of Client with static internal script hash. func (s StaticClient) TestInvoke(prm TestInvokePrm) ([]stackitem.Item, error) { return s.client.TestInvoke( s.scScriptHash, prm.method, prm.args..., ) } // ContractAddress returns the address of the associated contract. func (s StaticClient) ContractAddress() util.Uint160 { return s.scScriptHash } // TryNotary returns option to enable // notary invocation tries. func TryNotary() StaticClientOption { return func(o *staticOpts) { o.tryNotary = true } } // AsAlphabet returns option to sign main TX // of notary requests with client's private // key. // // Considered to be used by IR nodes only. func AsAlphabet() StaticClientOption { return func(o *staticOpts) { o.alpha = true } }