q9f / eth.rb

a straightforward library to build, sign, and broadcast ethereum transactions anywhere you can run ruby.
https://q9f.github.io/eth.rb
Apache License 2.0
200 stars 86 forks source link

How to call contract Functions and send Tx? #136

Closed Amplitude88 closed 1 year ago

Amplitude88 commented 2 years ago

Hi,

im new to this gem but i already have experience with other web3 SDK's. My Question is:

How can i call a contract function for example "SwapExactETHForToken" and put Token A and Token B into the payload? And how can i send the transaction afterwards? Sorry i dont understand the documentation.

I have use Eth::Contract.from_abi because i think that this is the wrapper for the contract itself. Then i want to use my client to build the transaction payload: client.transact(contract, "SwapExactETHForToken").

But it didnt work....

How can i put the TokenA and TokenB into the transaction?

For example in Javascript:

const tx_builder = await contract.methods.swapExactETHForTokens(
  0,
  [tokenSELL, tokenBUY],
  sender_address,
  expiryDate
);

let encoded_tx = tx_builder.encodeABI();

let transactionObject = {
  gas: 200000,
  gasPrice: web3.utils.toWei("10", "gwei"),
  data: encoded_tx,
  value: qty,
  from: sender_address,
  to: dexRouter,
  nonce: nonce,
};

after this i sign the transaction. Could you tell me if i can do the same with this Ruby Gem? Or could you explain to me how i can call a contract function and als put data into the payload to send the transaction afterwards?

Thanks in advance

gith-u-b commented 2 years ago

I have a similar problem my network is fuji test

run:

cli.transact_and_wait(contract, 'setGreeting', '123')

error:

/Users/sai/.rvm/gems/ruby-3.0.3/gems/eth-0.5.6/lib/eth/client.rb:469:in `send_command': the method eth_sendTransaction is not available (IOError)
gith-u-b commented 2 years ago

Really hope there is stable Ruby version for ETH operations

Amplitude88 commented 2 years ago

Dont think so. Even there is no activity to help the community. I'Ve also asked in the Ruby discord. Even no response. I ask myself what is gonna happen with the ruby community? Please help us

Liteo commented 2 years ago

I have a similar problem my network is fuji test

run:

cli.transact_and_wait(contract, 'setGreeting', '123')

error:

/Users/sai/.rvm/gems/ruby-3.0.3/gems/eth-0.5.6/lib/eth/client.rb:469:in `send_command': the method eth_sendTransaction is not available (IOError)

I don't know much either, but ... It looks like Fuji Test which doesn't support eth_sendTransaction. (For it to support that, it would need to know your private key, but it's a shared public node.)

You need to either sign the transaction yourself and then send via eth_sendRawTransaction or use a provider that can hold private keys like MetaMask in the browser.

Ex: contract = Eth::Contract.from_abi(abi: abi, address: contract_address, name: "yourContractName") contract.key = Eth::Key.new(priv: '0xyour_client_account_private_key') cli.transact(contract, 'setGreeting', '123', sender_key: contract.key)

Liteo commented 2 years ago

Dont think so. Even there is no activity to help the community. I'Ve also asked in the Ruby discord. Even no response. I ask myself what is gonna happen with the ruby community? Please help us

Looks like web3 is a radioactive topic in the ruby community, a real shame ...

Soleone commented 2 years ago

To help debug this issue could you please provide:

I spent hours today debugging my problem and got various obscure error messages (e.g. it started with ArgumentError: wrong number of arguments (given 1, expected 0) from <internal:nilclass>:10:in to_i') and it turned out all that was wrong was the URL provided to the ETH client, e.g. it should have been something like: https://eth-mainnet.alchemyapi.io/jsonrpc/18lHSAeZttzI4wJvHpXlJGHK4ZDNCTCP (provided incl. that auth token on https://explorer.alchemyapi.io). Example request that worked for me is from the README file for client.call(ens_registry, "old").

q9f commented 1 year ago

@sai1024 your issue is unrelated, the error message says that send_transaction is not available because you don't have access to the remote private keys. you would need to pass a key to sign the transaction as @Liteo explained:

https://github.com/q9f/eth.rb/issues/136#issuecomment-1187611738

@Amplitude88 I cannot comment on the Ruby Discord as I do not use Discord but feel free to join our Telegram group: https://t.me/ruby_eth

I understand the documentation is very minimal: https://github.com/q9f/eth.rb/wiki/Smart-Contracts#your-own-contract-from-file

Could you tell me what information you are missing though? What error message do you get?

Amplitude88 commented 1 year ago

@q9f

The main problem is, that im not in able to send any payload. I dont know how i can put the data into the payload. That means i dont know, how i can interact with an contract. For example i will call Eth::Contract.from_abi() , i will receive an object. This object give me the called contract, right?. Then i want to pass my transaction data, to build a transaction object which i then will sign and send afterwards. How can i do this?

q9f commented 1 year ago

@q9f

The main problem is, that im not in able to send any payload. I dont know how i can put the data into the payload. That means i dont know, how i can interact with an contract. For example i will call Eth::Contract.from_abi() , i will receive an object. This object give me the called contract, right?. Then i want to pass my transaction data, to build a transaction object which i then will sign and send afterwards. How can i do this?

Gotcha. So, you want to transact with the contract, not call it. See this answer: https://ethereum.stackexchange.com/a/768/87

But putting terminology aside, here is what you want to do. You don't want to create a transaction object, the library can handle this for you.

# Create a client / RPC endpoint and set default fees
goerli = Client.create "https://goerli.infura.io/v3/31b..30c"
# => #<Eth::Client::Http:0x000055a0ec933dd8
#  @gas_limit=21000,
#  @host="goerli.infura.io",
#  @id=0,
#  @max_fee_per_gas=0.2e11,
#  @max_priority_fee_per_gas=0,
#  @port=443,
#  @ssl=true,
#  @uri=#<URI::HTTPS https://goerli.infura.io/v3/31b..30c>>
goerli.max_priority_fee_per_gas = 2 * Unit::GWEI
# => 0.2e10
goerli.max_fee_per_gas = 69 * Unit::GWEI
# => 0.69e11

# Load some account to sign transactions with later
signer = JSON.load File.open "/path/to/UTC--2019-01-30T13-26-55.927410794Z--e0a2bd4258d2768837baa26a28fe71dc079f84c7-goerli"
# => {"address"=>"e0a2bd4258d2768837baa26a28fe71dc079f84c7",
#  "crypto"=>
#   {"cipher"=>"aes-128-ctr",
#    "ciphertext"=>"700..39c",
#    "cipherparams"=>{"iv"=>"555..00e"},
#    "kdf"=>"scrypt",
#    "kdfparams"=>
#     {"dklen"=>32,
#      "n"=>262144,
#      "p"=>1,
#      "r"=>8,
#      "salt"=>"2bb..93c"},
#    "mac"=>"dfc..079"},
#  "id"=>"3332ffa0-6511-43dd-a4e3-937b00e180d1",
#  "version"=>3}
signer = Key::Decrypter.perform signer, "password"
# => #<Eth::Key:0x000055b04afad430
# @private_key=
#  #<Secp256k1::PrivateKey:0x000055b04afac6c0
#   @data="G?*\..\x86">,
# @public_key=#<Secp256k1::PublicKey:0x000055b04afac670>>
signer.address.to_s
# => "0xe0a2Bd4258D2768837BAa26A28fE71Dc079f84c7"

# Create a contract from an ABI and address
house_address = Address.new "0x2d234b332E94257f6538290Ae335eEF94B0974F0"
# => #<Eth::Address:0x000055a0ec8b3ed0 @address="0x2d234b332E94257f6538290Ae335eEF94B0974F0">
house_name = "House"
# => "House"
house_abi = '[{"inputs":[{"internalType":"uint64","name":"_subscriptionId","type":"uint64"},{"internalType":"uint256","name":"_minBalance","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"InvalidBet","type":"error"},{"inputs":[],"name":"InvalidWithdraw","type":"error"},{"inputs":[{"internalType":"address","name":"have","type":"address"},{"internalType":"address","name":"want","type":"address"}],"name":"OnlyCoordinatorCanFulfill","type":"error"},{"inputs":[],"name":"RequestError","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"rebet","type":"bool"},{"indexed":false,"internalType":"uint8","name":"direction","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"result","type":"uint8"},{"indexed":false,"internalType":"address","name":"better","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Result","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"cashOut","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_player","type":"address"}],"name":"getWinnings","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"_direction","type":"uint8"}],"name":"makeBet","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"requestId","type":"uint256"},{"internalType":"uint256[]","name":"randomWords","type":"uint256[]"}],"name":"rawFulfillRandomWords","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"requestIds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"}],"name":"setOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_vrf","type":"address"}],"name":"setVRFCoordinator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_to","type":"address"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]'
# => "[{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"_subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"uint256\",\"name\":\"_minBalance\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"InvalidBet\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidWithdraw\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"have\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"want\",\"type\":\"address\"}],\"name\":\"OnlyCoordinatorCanFulfill\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RequestError\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"rebet\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"direction\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"result\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"better\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Result\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"cashOut\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_player\",\"type\":\"address\"}],\"name\":\"getWinnings\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"_direction\",\"type\":\"uint8\"}],\"name\":\"makeBet\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"randomWords\",\"type\":\"uint256[]\"}],\"name\":\"rawFulfillRandomWords\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"requestIds\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_vrf\",\"type\":\"address\"}],\"name\":\"setVRFCoordinator\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_to\",\"type\":\"address\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]"
house = Eth::Contract.from_abi(name: house_name, address: house_address, abi: house_abi)
# => #<Eth::Contract::House:0x000055a0ec4bcf98>
house.address.to_s
# => "0x2d234b332E94257f6538290Ae335eEF94B0974F0"

# To read the contract, use `call`
goerli.call(house, "getBalance")
# => 0
goerli.call(house, "getWinnings", Key.new.address.to_s)
# => 0

# To write to the contract, use `transact` (or better `transact_and_wait`)
goerli.transact_and_wait(house, "cashOut", 999999999999999999, sender_key: signer)
# => "0x67e9b96b99e51737231c17bc492e37e023e87372253627647ea4a2a7ebf04918"
goerli.transact_and_wait(house, "withdraw", 999999999999999999, Key.new.address.to_s, sender_key: signer)
# => "0x7032d1bcc23b8f6881eabdbf64ddbb22ae333ad6c02027d8dc5924c0445bdf24"

Note, how you can just pass arguments to the function call and transact. Don't forget to provide a sender key.

Please let me know if this is more clear now.

q9f commented 1 year ago

I added it to the wiki: https://github.com/q9f/eth.rb/wiki/Smart-Contracts#writing-to-a-smart-contract-transact

Let me know if there is anything else!