evm/token.go

141 lines
3.9 KiB
Go

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())
}