package evm import ( "context" "math/big" "git.lehouerou.net/laurent/evm/contracts" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" "github.com/shopspring/decimal" ) type Token interface { Client() Client Address() common.Address Name() string Symbol() string Decimals() uint8 TotalSupply() (decimal.Decimal, error) BalanceOf(common.Address) (decimal.Decimal, error) Balance() (decimal.Decimal, error) Allowance(owner common.Address, spender common.Address) (decimal.Decimal, error) Approve(ctx context.Context, spender common.Address, value decimal.Decimal, opts ...ExecutionOption) (Transaction, error) Transfer(ctx context.Context, to common.Address, value decimal.Decimal, opts ...ExecutionOption) (Transaction, error) ValueToBigInt(value decimal.Decimal) *big.Int ValueFromBigInt(value *big.Int) decimal.Decimal } type baseToken struct { client Client contract *contracts.Token address common.Address name string symbol string decimals uint8 } func NewToken(client Client, address common.Address) (Token, error) { contract, err := contracts.NewToken(address, client) if err != nil { return nil, errors.WithStack(err) } name, err := contract.Name(&bind.CallOpts{}) if err != nil { return nil, errors.WithStack(err) } symbol, err := contract.Symbol(&bind.CallOpts{}) if err != nil { return nil, errors.WithStack(err) } decimals, err := contract.Decimals(&bind.CallOpts{}) if err != nil { return nil, errors.WithStack(err) } return &baseToken{ address: address, name: name, symbol: symbol, decimals: decimals, client: client, contract: contract, }, nil } func (t *baseToken) Client() Client { return t.client } func (t *baseToken) TotalSupply() (decimal.Decimal, error) { s, err := t.contract.TotalSupply(&bind.CallOpts{}) if err != nil { return decimal.Zero, errors.Wrap(err, "calling contract") } return t.ValueFromBigInt(s), nil } func (t *baseToken) BalanceOf(address common.Address) (decimal.Decimal, error) { b, err := t.contract.BalanceOf(&bind.CallOpts{}, address) if err != nil { return decimal.Zero, errors.Wrap(err, "getting balance from contract") } return t.ValueFromBigInt(b), nil } func (t *baseToken) Allowance(owner common.Address, spender common.Address) (decimal.Decimal, error) { a, err := t.contract.Allowance(&bind.CallOpts{}, owner, spender) if err != nil { return decimal.Zero, errors.Wrap(err, "calling contract") } return t.ValueFromBigInt(a), nil } func (t *baseToken) Approve(ctx context.Context, spender common.Address, value decimal.Decimal, opts ...ExecutionOption) (Transaction, error) { return t.client.Execute(ctx, func(ctx context.Context, options *TransactOpts) (*types.Transaction, error) { return t.contract.Approve(options.TransactOpts, spender, t.ValueToBigInt(value)) }, opts...) } func (t *baseToken) Transfer(ctx context.Context, to common.Address, value decimal.Decimal, opts ...ExecutionOption) (Transaction, error) { return t.client.Execute(ctx, func(ctx context.Context, options *TransactOpts) (*types.Transaction, error) { return t.contract.Transfer(options.TransactOpts, to, t.ValueToBigInt(value)) }, opts...) } func (t *baseToken) Address() common.Address { return t.address } func (t *baseToken) Name() string { return t.name } func (t *baseToken) Symbol() string { return t.symbol } func (t *baseToken) Decimals() uint8 { return t.decimals } func (t *baseToken) DecimalsAsInt32() int32 { return int32(t.decimals) } func (t *baseToken) ValueFromBigInt(val *big.Int) decimal.Decimal { return decimal.NewFromBigInt(val, -t.DecimalsAsInt32()) } func (t *baseToken) ValueToBigInt(val decimal.Decimal) *big.Int { return val.Shift(t.DecimalsAsInt32()).BigInt() } func (t *baseToken) Balance() (decimal.Decimal, error) { return t.BalanceOf(t.client.PublicAddress()) }