Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,22 @@ import (

var (
appVersion = "v1.2.0"
chainIDMap = map[string]int{"sepolia": 11155111, "holesky": 17000, "opengradient": 10740}
chainIDMap = map[string]int{"sepolia": 11155111, "holesky": 17000, "opengradient": 10740, "base_sepolia": 84532}

httpPortFlag = flag.Int("httpport", 8090, "Listener port to serve HTTP connection")
proxyCntFlag = flag.Int("proxycount", 1, "Count of reverse proxies in front of the server")
versionFlag = flag.Bool("version", false, "Print version number")

payoutFlag = flag.Float64("faucet.amount", 0.03, "Number of Ethers to transfer per user request")
intervalFlag = flag.Int("faucet.minutes", 300, "Number of minutes to wait between funding rounds")
netnameFlag = flag.String("faucet.name", "opengradient", "Network name to display on the frontend")
symbolFlag = flag.String("faucet.symbol", "OGETH", "Token symbol to display on the frontend")
payoutFlag = flag.Float64("faucet.amount", 0.1, "Number of tokens to transfer per user request")
intervalFlag = flag.Int("faucet.minutes", 300, "Number of minutes to wait between funding rounds")
netnameFlag = flag.String("faucet.name", "base_sepolia", "Network name to display on the frontend")
symbolFlag = flag.String("faucet.symbol", "OPG", "Token symbol to display on the frontend")
tokenAddrFlag = flag.String("faucet.tokenaddr", "0x240b09731D96979f50B2C649C9CE10FcF9C7987F", "ERC-20 token contract address to disperse (empty for native ETH)")

keyJSONFlag = flag.String("wallet.keyjson", os.Getenv("KEYSTORE"), "Keystore file to fund user requests with")
keyPassFlag = flag.String("wallet.keypass", "password.txt", "Passphrase text file to decrypt keystore")
privKeyFlag = flag.String("wallet.privkey", os.Getenv("PRIVATE_KEY"), "Private key hex to fund user requests with")
providerFlag = flag.String("wallet.provider", "https://ogevmdevnet.opengradient.ai", "Endpoint for Ethereum JSON-RPC connection")
providerFlag = flag.String("wallet.provider", "https://sepolia.base.org", "Endpoint for Ethereum JSON-RPC connection")

hcaptchaSiteKeyFlag = flag.String("hcaptcha.sitekey", os.Getenv("HCAPTCHA_SITEKEY"), "hCaptcha sitekey")
hcaptchaSecretFlag = flag.String("hcaptcha.secret", os.Getenv("HCAPTCHA_SECRET"), "hCaptcha secret")
Expand All @@ -56,7 +57,7 @@ func Execute() {
chainID = big.NewInt(int64(value))
}

txBuilder, err := chain.NewTxBuilder(*providerFlag, privateKey, chainID)
txBuilder, err := chain.NewTxBuilder(*providerFlag, privateKey, chainID, *tokenAddrFlag)
if err != nil {
panic(fmt.Errorf("cannot connect to web3 provider: %w", err))
}
Expand Down
44 changes: 39 additions & 5 deletions internal/chain/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ type TxBuild struct {
fromAddress common.Address
nonce uint64
supportsEIP1559 bool
tokenAddress *common.Address
}

func NewTxBuilder(provider string, privateKey *ecdsa.PrivateKey, chainID *big.Int) (TxBuilder, error) {
func NewTxBuilder(provider string, privateKey *ecdsa.PrivateKey, chainID *big.Int, tokenAddr string) (TxBuilder, error) {
client, err := ethclient.Dial(provider)
if err != nil {
return nil, err
Expand All @@ -54,6 +55,12 @@ func NewTxBuilder(provider string, privateKey *ecdsa.PrivateKey, chainID *big.In
fromAddress: crypto.PubkeyToAddress(privateKey.PublicKey),
supportsEIP1559: supportsEIP1559,
}

if tokenAddr != "" {
addr := common.HexToAddress(tokenAddr)
txBuilder.tokenAddress = &addr
}

txBuilder.refreshNonce(context.Background())

return txBuilder, nil
Expand All @@ -68,13 +75,24 @@ func (b *TxBuild) Transfer(ctx context.Context, to string, value *big.Int) (comm
toAddress := common.HexToAddress(to)
nonce := b.getAndIncrementNonce()

// Determine transaction target and data based on whether this is an ERC-20 transfer
txTo := &toAddress
txValue := value
var txData []byte

if b.tokenAddress != nil {
txTo = b.tokenAddress
txValue = big.NewInt(0)
txData = buildERC20TransferData(toAddress, value)
}

var err error
var unsignedTx *types.Transaction

if b.supportsEIP1559 {
unsignedTx, err = b.buildEIP1559Tx(ctx, &toAddress, value, gasLimit, nonce)
unsignedTx, err = b.buildEIP1559Tx(ctx, txTo, txValue, gasLimit, nonce, txData)
} else {
unsignedTx, err = b.buildLegacyTx(ctx, &toAddress, value, gasLimit, nonce)
unsignedTx, err = b.buildLegacyTx(ctx, txTo, txValue, gasLimit, nonce, txData)
}

if err != nil {
Expand All @@ -101,7 +119,21 @@ func (b *TxBuild) Transfer(ctx context.Context, to string, value *big.Int) (comm
return signedTx.Hash(), nil
}

func (b *TxBuild) buildEIP1559Tx(ctx context.Context, to *common.Address, value *big.Int, gasLimit uint64, nonce uint64) (*types.Transaction, error) {
// buildERC20TransferData constructs calldata for ERC-20 transfer(address,uint256)
func buildERC20TransferData(to common.Address, amount *big.Int) []byte {
// Function selector: keccak256("transfer(address,uint256)") = 0xa9059cbb
methodID := []byte{0xa9, 0x05, 0x9c, 0xbb}
paddedAddress := common.LeftPadBytes(to.Bytes(), 32)
paddedAmount := common.LeftPadBytes(amount.Bytes(), 32)

var data []byte
data = append(data, methodID...)
data = append(data, paddedAddress...)
data = append(data, paddedAmount...)
return data
}

func (b *TxBuild) buildEIP1559Tx(ctx context.Context, to *common.Address, value *big.Int, gasLimit uint64, nonce uint64, data []byte) (*types.Transaction, error) {
header, err := b.client.HeaderByNumber(ctx, nil)
if err != nil {
return nil, err
Expand All @@ -124,10 +156,11 @@ func (b *TxBuild) buildEIP1559Tx(ctx context.Context, to *common.Address, value
Gas: gasLimit,
To: to,
Value: value,
Data: data,
}), nil
}

func (b *TxBuild) buildLegacyTx(ctx context.Context, to *common.Address, value *big.Int, gasLimit uint64, nonce uint64) (*types.Transaction, error) {
func (b *TxBuild) buildLegacyTx(ctx context.Context, to *common.Address, value *big.Int, gasLimit uint64, nonce uint64, data []byte) (*types.Transaction, error) {
gasPrice, err := b.client.SuggestGasPrice(ctx)
if err != nil {
return nil, err
Expand All @@ -142,6 +175,7 @@ func (b *TxBuild) buildLegacyTx(ctx context.Context, to *common.Address, value *
Gas: gasLimit,
To: to,
Value: value,
Data: data,
}), nil
}

Expand Down
11 changes: 5 additions & 6 deletions web/src/Faucet.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
hcaptchaLoaded = true;
};

$: document.title = "OpenGradient Devnet Faucet";
$: document.title = "$" + faucetInfo.symbol + " Faucet on " + capitalize(faucetInfo.network.replace('_', ' '));

let widgetID;
$: if (mounted && hcaptchaLoaded) {
Expand Down Expand Up @@ -107,8 +107,7 @@
}

function capitalize(str) {
const lower = str.toLowerCase();
return str.charAt(0).toUpperCase() + lower.slice(1);
return str.toLowerCase().split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
}
</script>

Expand All @@ -132,17 +131,17 @@
<span class="icon">
<i class="fa fa-bath" />
</span>
<span><b>OpenGradient Devnet Faucet</b></span>
<span><b>${faucetInfo.symbol} Faucet on {capitalize(faucetInfo.network.replace('_', ' '))}</b></span>
</a>
</div>
<div id="navbarMenu" class="navbar-menu">
<div class="navbar-end">
<span class="navbar-item">
<a
class="button is-white is-outlined"
href="https://opengradient.ai"
href="https://base.org"
>
<span>opengradient.ai</span>
<span>Base Sepolia</span>
</a>
</span>
</div>
Expand Down