diff --git a/adapters/adapters.go b/adapters/adapters.go index e9c0db6..0843230 100644 --- a/adapters/adapters.go +++ b/adapters/adapters.go @@ -59,7 +59,8 @@ type Lock struct { TxHash common.Hash } -// Position is used for logx trading campaign +// Position is used for airdrop or trading campaign. +// For examples, see Avalon. type Position struct { User common.Address TokenAmount *big.Int @@ -70,7 +71,7 @@ type Position struct { TxHash common.Hash } -// Prdiction is used for prediction campaign. +// Prediction is used for prediction campaign. // For examples, see Robinos. type Prediction struct { User common.Address diff --git a/adapters/projects/avalon/claim.go b/adapters/projects/avalon/claim.go new file mode 100644 index 0000000..8a46a63 --- /dev/null +++ b/adapters/projects/avalon/claim.go @@ -0,0 +1,98 @@ +package avalon + +import ( + "context" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/taikoxyz/trailblazer-adapters/adapters" + "github.com/taikoxyz/trailblazer-adapters/adapters/contracts/erc20" +) + +const ( + // TODO: update + AvalonAirdropAddress string = "0x46f0a2e45bee8e9ebfdb278ce06caa6af294c349" + AvalonTokenAddress string = "0x46f0a2e45bee8e9ebfdb278ce06caa6af294c349" + AvalonTokenDecimal = 18 + + logTransferSignature string = "Transfer(address,address,uint256)" +) + +type ClaimIndexer struct { + client *ethclient.Client + addresses []common.Address + contract common.Address +} + +func NewClaimIndexer(client *ethclient.Client, contract common.Address, addresses []common.Address) *ClaimIndexer { + return &ClaimIndexer{ + client: client, + addresses: addresses, + contract: contract, + } +} + +var _ adapters.LogIndexer[adapters.Position] = &ClaimIndexer{} + +func (indexer *ClaimIndexer) Addresses() []common.Address { + return indexer.addresses +} + +func (indexer *ClaimIndexer) Index(ctx context.Context, logs ...types.Log) ([]adapters.Position, error) { + var transferEvent struct { + Value *big.Int + } + + var claims []adapters.Position + + for _, l := range logs { + if !indexer.isTransfer(l) { + continue + } + + from := common.BytesToAddress(l.Topics[1].Bytes()[12:]) + to := common.BytesToAddress(l.Topics[2].Bytes()[12:]) + + if from.Hex() != indexer.contract.Hex() { + continue + } + + erc20ABI, err := abi.JSON(strings.NewReader(erc20.ABI)) + if err != nil { + return nil, err + } + + err = erc20ABI.UnpackIntoInterface(&transferEvent, "Transfer", l.Data) + if err != nil { + return nil, err + } + + block, err := indexer.client.BlockByNumber(ctx, big.NewInt(int64(l.BlockNumber))) + if err != nil { + return nil, err + } + + claim := &adapters.Position{ + User: to, + TokenAmount: transferEvent.Value, + TokenDecimals: AvalonTokenDecimal, + Token: common.HexToAddress(AvalonTokenAddress), + BlockTime: block.Time(), + BlockNumber: block.NumberU64(), + TxHash: l.TxHash, + } + + claims = append(claims, *claim) + } + + return claims, nil +} + +func (indexer *ClaimIndexer) isTransfer(l types.Log) bool { + return l.Topics[0].Hex() == crypto.Keccak256Hash([]byte(logTransferSignature)).Hex() +} diff --git a/adapters/projects/avalon/claim_test.go b/adapters/projects/avalon/claim_test.go new file mode 100644 index 0000000..2695d8f --- /dev/null +++ b/adapters/projects/avalon/claim_test.go @@ -0,0 +1,40 @@ +package avalon_test + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/taikoxyz/trailblazer-adapters/adapters" + "github.com/taikoxyz/trailblazer-adapters/adapters/projects/avalon" +) + +func TestClaimIndexer(t *testing.T) { + taikoRPC := "https://rpc.taiko.xyz" + blocknumber := int64(445053) + + ctx := context.Background() + + client, err := ethclient.Dial(taikoRPC) + require.NoError(t, err) + + indexer := avalon.NewClaimIndexer(client, common.HexToAddress(avalon.AvalonAirdropAddress), []common.Address{common.HexToAddress(avalon.AvalonTokenAddress)}) + + logs, err := adapters.GetLogs(ctx, client, indexer.Addresses(), blocknumber) + require.NoError(t, err) + + locks, err := indexer.Index(ctx, logs...) + assert.NoError(t, err) + assert.Len(t, locks, 1) + assert.Equal(t, common.HexToAddress("0xC3204E92B0e7731d75Ad667a93c8Da815BD9Ac61"), locks[0].User) + assert.Equal(t, big.NewInt(2000000000000000000), locks[0].TokenAmount) + assert.Equal(t, adapters.TaikoTokenDecimals, locks[0].TokenDecimals) + assert.Equal(t, common.HexToAddress(adapters.TaikoTokenAddress), locks[0].Token) + assert.Equal(t, uint64(1728390191), locks[0].BlockTime) + assert.Equal(t, uint64(blocknumber), locks[0].BlockNumber) + assert.Equal(t, common.HexToHash("0x95f528b52f0a75176543f516014bbba26e003f1c17c9b9413e936240e3f44650"), locks[0].TxHash) +} diff --git a/cmd/adapter.go b/cmd/adapter.go index 8b2fde5..2d740df 100644 --- a/cmd/adapter.go +++ b/cmd/adapter.go @@ -23,6 +23,8 @@ const ( RobinosPrediction adapter = "RobinosPrediction" LoopringLock adapter = "LoopringLock" PolarisLP adapter = "PolarisLP" + DoraHacksVoting adapter = "DoraHacksVoting" + AvalonClaim adapter = "AvalonClaim" ) func adapterz() []adapter { @@ -43,5 +45,7 @@ func adapterz() []adapter { RobinosPrediction, LoopringLock, PolarisLP, + DoraHacksVoting, + AvalonClaim, } } diff --git a/cmd/cmd.go b/cmd/cmd.go index 742138e..4a76334 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -13,8 +13,10 @@ import ( "github.com/spf13/cobra" "github.com/taikoxyz/trailblazer-adapters/adapters" nftdeployed "github.com/taikoxyz/trailblazer-adapters/adapters/nft_deployed" + "github.com/taikoxyz/trailblazer-adapters/adapters/projects/avalon" "github.com/taikoxyz/trailblazer-adapters/adapters/projects/conft" "github.com/taikoxyz/trailblazer-adapters/adapters/projects/domains" + "github.com/taikoxyz/trailblazer-adapters/adapters/projects/dorahacks" "github.com/taikoxyz/trailblazer-adapters/adapters/projects/drips" "github.com/taikoxyz/trailblazer-adapters/adapters/projects/gaming" "github.com/taikoxyz/trailblazer-adapters/adapters/projects/izumi" @@ -201,6 +203,19 @@ func executeCommand(p prompt) error { []common.Address{common.HexToAddress(polaris.VaultAddress)}, ) return processLog(ctx, client, indexer, p.Blocknumber) + case DoraHacksVoting: + indexer := dorahacks.NewVotingIndexer( + client, + []common.Address{common.HexToAddress(dorahacks.VotingAddress)}, + ) + return processLog(ctx, client, indexer, p.Blocknumber) + case AvalonClaim: + indexer := avalon.NewClaimIndexer( + client, + common.HexToAddress(avalon.AvalonAirdropAddress), + []common.Address{common.HexToAddress(avalon.AvalonTokenAddress)}, + ) + return processLog(ctx, client, indexer, p.Blocknumber) default: return fmt.Errorf("adapter %s is not supported", p.Adapter)