Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

python web3 to interact with gmx #109

Open
daqingsu19 opened this issue Feb 15, 2023 · 11 comments
Open

python web3 to interact with gmx #109

daqingsu19 opened this issue Feb 15, 2023 · 11 comments

Comments

@daqingsu19
Copy link

i tried to use python web3 to open long position on gmx, but keep getting errors about format:
the gmx.usdc etc are the usdc address on arbitrum and i downloaded the ABIs.
gmx_position_router_contract.functions.createIncreasePosition(
_path = [gmx.usdc, gmx.weth],
_indexToken = gmx.weth,
_amountIn = 910**6,
_minOut = 0,
_sizeDelta = 18
1030,
_isLong = True,
_acceptablePrice = 1670*10
30,
_executionFee = 1*10**14,
_referralCode = bytes(0),
_callbackTarget = '0x0').buildTransaction(....)
web3.exceptions.ValidationError:
Could not identify the intended function with name createIncreasePosition, positional argument(s) of type () and keyword argument(s) of type {'_path': <class 'list'>, '_indexToken': <class 'str'>, '_amountIn': <class 'int'>, '_minOut': <class 'int'>, '_sizeDelta': <class 'int'>, '_isLong': <class 'bool'>, '_acceptablePrice': <class 'int'>, '_executionFee': <class 'int'>, '_referralCode': <class 'bytes'>, '_callbackTarget': <class 'str'>}.
Found 1 function(s) with the name createIncreasePosition: ['createIncreasePosition(address[],address,uint256,uint256,uint256,bool,uint256,uint256,bytes32,address)']
Function invocation failed due to no matching argument types.
is there any python example to do this:
https://gmxio.gitbook.io/gmx/contracts#opening-increasing-a-position

@daqingsu19
Copy link
Author

figured out...format issue. this works:
_referralCode=bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000'),
_callbackTarget='0x0000000000000000000000000000000000000000'

@daqingsu19 daqingsu19 reopened this Feb 16, 2023
@daqingsu19
Copy link
Author

txn was mined but still got error:
https://arbiscan.io/tx/0x9fde90302e8e46705781e295ead6fe9d41ce3316fd93c9968eeccf6bde78d8b3
this is how i call the function:
createIncreasePosition(
_path=[gmx.usdc, gmx.weth],
_indexToken=gmx.weth,
_amountIn= 10000000,
_minOut= 0,
_sizeDelta= 10000000000000000000000000000000,
_isLong= True,
_acceptablePrice= 1698660740000000000000000000000000,
_executionFee= 100000000000000,
_referralCode= '0x0000000000000000000000000000000000000000000000000000000000000000',
_callbackTarget= '0x0000000000000000000000000000000000000000').buildTransaction({ "gas": 10000000,
"maxFeePerGas": "0x3b9aca00",
"maxPriorityFeePerGas": "0x12a05f200",
"nonce": gmx.web3.eth.getTransactionCount(gmx.account.address),
'from': gmx.account.address})

to use 10 usdc to open long eth position

@daqingsu19
Copy link
Author

@vianiGa can you take a look?

@RoscoeTheDog
Copy link

RoscoeTheDog commented Oct 18, 2023

bump did you figure this out? Trying to essentially do the same on web3 python. I'm running into web3.exceptions.ContractLogicError: execution reverted: val when at estimated_gas = tx.estimate_gas({ 'from': MY_ADDRESS })

def increase_position(base_address, quote_address, direction, amount, slippage=0.5):
    # normalize direction param
    direction = str.lower(direction)

    create_increase_position_abi = abi.arb.gmx.positionRouter.abi  # ABI for the `createIncreasePosition` function
    position_router_contract = w3.eth.contract(address=abi.arb.gmx.positionRouter.address, abi=create_increase_position_abi)

    acceptable_price = 0
    try:
        for s in fetch_tickers([base_address, quote_address]):
            if s.get('tokenAddress') == base_address:
                acceptable_price = int(float(s.get('maxPrice')) * (1 + slippage/100))
    except Exception as e:
        print('failed to fetch tickers, aborting', print(e))
        return None

    params = {
        '_path': [quote_address],  # Make sure this is a list.
        '_indexToken': base_address,
        '_amountIn': w3.to_wei(amount, 'mwei'),
        '_minOut': 0,
        '_sizeDelta': int(amount * (10 ** 30)),
        '_isLong': True if direction == 'long' else False,  # Convert this directly to a bool here
        '_acceptablePrice': acceptable_price,
        '_executionFee': position_router_contract.functions.minExecutionFee().call(),
        '_referralCode': w3.to_bytes(text='0').rjust(32, b'\0'),
        '_callbackTarget': '0x0000000000000000000000000000000000000000'
    }

    tx = position_router_contract.functions.createIncreasePosition(
        params['_path'],
        params['_indexToken'],
        params['_amountIn'],
        params['_minOut'],
        params['_sizeDelta'],
        params['_isLong'],
        params['_acceptablePrice'],
        params['_executionFee'],
        params['_referralCode'],
        params['_callbackTarget']
    )
    estimated_gas = tx.estimate_gas({
        'from': MY_ADDRESS
    })
    buffered_gas = int(estimated_gas * 1.20)
    tx.build_transaction({
        'chainId': 42161,
        'gas': buffered_gas,
        'gasPrice': w3.eth.gas_price,
        'nonce': w3.eth.get_transaction_count(MY_ADDRESS),
    })

    signed_tx = w3.eth.account.sign_transaction(tx, MY_PRIVATE_KEY)
    return w3.eth.send_raw_transaction(signed_tx.rawTransaction).hex()
    
   if __name__ == '__main__':
    receipt = increase_position(base_address=token_contracts.arbitrum.ARB,      # token_contracts.arbitrum.ARB,
                                quote_address=token_contracts.arbitrum.USDC,     # token_contracts.arbitrum.USDC,
                                direction='long',
                                amount=5)

@salparadi
Copy link

According to the contract, you need to send the _executionFee amount as the transaction value. So if _executionFee is 180000000000000, you'd need to send that amount of ETH. You can check the contract here. Look in PositionRouter.sol on line 306. I think you would just add 'value': 180000000000000 to your estimate_gas dict.

@RoscoeTheDog
Copy link

According to the contract, you need to send the _executionFee amount as the transaction value. So if _executionFee is 180000000000000, you'd need to send that amount of ETH. You can check the contract here. Look in PositionRouter.sol on line 306. I think you would just add 'value': 180000000000000 to your estimate_gas dict.

Thanks. Putting that in the dictionary did get me passed that error. But now its also erroring web3.exceptions.ContractLogicError: execution reverted: ERC20: transfer amount exceeds allowance. This is confusing. Does it mean the allowance for the symbol is not permitted yet or does it mean I not have enough etherium? I'm testing on the arbitrum network and have roughly $15 of ether to play with, I should be able to open a position with ~$2.00 worth when using the front-end UI.

    estimated_gas = tx.estimate_gas({
        'from': MY_ADDRESS,
        'value': position_router_contract.functions.minExecutionFee().call()
    })

@RoscoeTheDog
Copy link

Here's me trying to approve a token to the maxuint256, getting web3.exceptions.ContractLogicError: execution reverted: Governable: forbidden.

# 3. Approve the Router contract for the token and amount
def approve_router(token_address):
    # Assume a standard ERC20 approve function
    approve_abi = abi.arb.gmx.positionRouter.abi  # ABI for the `approve` function of an ERC20 token
    token_contract = w3.eth.contract(address=abi.arb.gmx.positionRouter.address, abi=approve_abi)
    tx = token_contract.functions.approve(token_address, MY_ADDRESS, 2**256 - 1)
    estimated_gas = tx.estimate_gas({'from': MY_ADDRESS})
    buffered_gas = int(estimated_gas * 1.20)
    tx = tx.build_transaction({
        'chainId': 42161,
        'gas': buffered_gas,
        'gasPrice': w3.eth.gas_price,
        'nonce': w3.eth.get_transaction_count(MY_ADDRESS),
    })

    signed_tx = w3.eth.account.sign_transaction(tx, MY_PRIVATE_KEY)
    return w3.eth.send_raw_transaction(signed_tx.rawTransaction).hex()

receipt = approve_router(token_contracts.arbitrum.ETH)

@salparadi
Copy link

I think you're doing it backwards? Your token_contract is being set to the position router, when it should be the address of whatever token. Then you should be approving the position router as a spender for the token. I'm not sure if your approve abi is correct either, not sure how you have that defined but it appears to be the abi of the position router?

@RoscoeTheDog
Copy link

RoscoeTheDog commented Oct 21, 2023

Ah yea that was a chicken scratch mistake-- with labeling things incorrectly. I've fixed it but now you got me a bit confused.. Is it my wallet address or the position router contract's address passed for approve functions second argument? I am using the approve function from the positionRouter contract. You can see the function signature on arbiscan and says the "spender" is the second argument.

The positionRouter.abi is equivalent to the "abi" section of the contract 0xb87a436B93fFE9D75c5cFA7bAcFff96430b09868 found on arbiscan. Using web3, we serialize that data back into an object, then call the approve method and pass down the arguments as needed. Still confused if the second argument is my wallet or the contracts.. I've tried both and still get that execution reverted: Governable: forbidden error.

def approve_router(token_address):
    approve_abi = abi.arb.gmx.positionRouter.abi
    position_router_contract = w3.eth.contract(address=abi.arb.gmx.positionRouter.address, abi=approve_abi)
    tx = position_router_contract.functions.approve(token_address, abi.arb.gmx.positionRouter.address, 2**256 - 1)
    estimated_gas = tx.estimate_gas({'from': MY_ADDRESS})
    buffered_gas = int(estimated_gas * 1.20)
    tx = tx.build_transaction({
        'chainId': 42161,
        'gas': buffered_gas,
        'gasPrice': w3.eth.gas_price,
        'nonce': w3.eth.get_transaction_count(MY_ADDRESS),
    })
    signed_tx = w3.eth.account.sign_transaction(tx, MY_PRIVATE_KEY)
    return w3.eth.send_raw_transaction(signed_tx.rawTransaction).hex()
    
receipt = approve_router(token_contracts.arbitrum.ETH)

I was able to allow plugins successfully, targeting the position router contract as the target and the code below.

def approve_plugin():
    approve_plugin_abi = abi.arb.gmx.router.abi # ABI for the `approvePlugin` function
    position_router_contract = w3.eth.contract(address=abi.arb.gmx.router.address, abi=approve_plugin_abi)
    tx = position_router_contract.functions.approvePlugin(abi.arb.gmx.positionRouter.address)
    estimated_gas = tx.estimate_gas({'from': MY_ADDRESS})
    buffered_gas = int(estimated_gas * 1.20)
    tx = tx.build_transaction({
        'chainId': 42161,
        'gas': buffered_gas,
        'gasPrice': w3.eth.gas_price,
        'nonce': w3.eth.get_transaction_count(MY_ADDRESS),
    })

    signed_tx = w3.eth.account.sign_transaction(tx, MY_PRIVATE_KEY)
    return w3.eth.send_raw_transaction(signed_tx.rawTransaction).hex()

Not sure what I'm doing wrong to allow spending ** shrug **

@salparadi
Copy link

It's the approve that's wrong, it's still backwards. Above, you are calling approve on the router contract. You need to do the opposite. You need to call approve on the token contract, and pass the router address in as the first argument, and the amount as the second.

Would look something like this:

def approve_router(token_address):
    token_abi = abi.arb.erc20.abi (or whatever)
    token_contract = w3.eth.contract(address=token_address, abi=token_abi)
    tx = token_contract.functions.approve(abi.arb.gmx.positionRouter.address, 2**256 - 1)
    ....

@RoscoeTheDog
Copy link

RoscoeTheDog commented Oct 21, 2023

Thank you for the guidance. I apologize I'm still a little new to web3 / smart contracts with python. I didn't think the token_address would have an ABI since upon viewing the contracts on arbiscan I could see there were basically 0 read/write functions for that token itself. I was unaware of native abi methods from erc20 standard.

I'm still running into another issue while trying to open positions :( I found this position manager library from a community member from https://github.com/SentryApe/sentry-gmx-python/blob/main/gmx.py . The snippit below is from it.

def send_tx(pk, addy, tx):
    signed_tx = w3.eth.account.sign_transaction(tx, pk)
    tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction)
    print(w3.to_hex(tx_hash))

def marketLong(coin, collateral, leverage, amount_in, price, slippage):
    print("Longboi")
    nonce = w3.eth.get_transaction_count(MY_ADDRESS)
    path = long_collateral_path(coin, collateral)
    execution_price = Web3.to_wei(str(price * (1.0 + slippage / 100.0)), "tether")
    is_long = True
    executionFee = Web3.to_wei( 300000, "gwei")
    referral = "0x0000000000000000000000000000000000000000000000000000000000000000"
    create_increase_position_abi = abi.arb.gmx.positionRouter.abi  # ABI for the `createIncreasePosition` function
    position_router_contract = w3.eth.contract(address=abi.arb.gmx.positionRouter.address, abi=create_increase_position_abi)
    if (coin == "eth" and collateral == "weth"):
        index_token = Web3.to_checksum_address(token_contracts.arbitrum.WETH.lower())
        min_out = 0
        amountIn = Web3.to_wei(amount_in, 'ether')
        size_delta = Web3.to_wei(str(leverage * amount_in * price *  (1.0 + slippage / 100.0)), 'tether')
        print(path, ", ",index_token, ", ",   ",", min_out, ", ", size_delta, ", ", is_long, ", ", execution_price, ", ", )
        longboi = position_router_contract.functions.createIncreasePosition(path, index_token, amountIn,  min_out,  size_delta,  is_long, execution_price, executionFee, referral, token_contracts.arbitrum.ADDRESS_ZERO).build_transaction({'chainId': 42161, 'nonce': nonce, 'value' : executionFee,'gasPrice': Web3.to_wei('1', 'gwei')})
        send_tx(MY_PRIVATE_KEY, position_router_contract, longboi)
    elif (coin == "eth" and (collateral == "usdt" or collateral == "usdc")):
        index_token = Web3.to_checksum_address(token_contracts.arbitrum.WETH.lower())
        min_out = Web3.to_wei(amount_in / (price * (1.0 + slippage / 100.0)) , 'ether')
        amountIn = Web3.to_wei(amount_in , 'mwei')
        size_delta = Web3.to_wei(str(leverage * amount_in  *  (1.0 + slippage / 100.0)), 'tether')
        print(path, ", ",index_token, ", ",   ",", min_out, ", ", size_delta, ", ", is_long, ", ", execution_price, ", ", )
        longboi = position_router_contract.functions.createIncreasePosition(path, index_token, amountIn,  min_out,  size_delta,  is_long, execution_price, executionFee, referral, token_contracts.arbitrum.ADDRESS_ZERO).build_transaction({'chainId': 42161, 'nonce': nonce, 'value' : executionFee,'gasPrice': Web3.to_wei('1', 'gwei')})
        send_tx(MY_PRIVATE_KEY, position_router_contract, longboi)
    elif (coin == "btc" and collateral == "btc"):
        index_token = Web3.to_checksum_address(token_contracts.arbitrum.BTC.lower())
        min_out = 0
        amountIn = Web3.to_wei(amount_in * 100, 'mwei')
        size_delta = Web3.to_wei(str(leverage * amount_in * price *  (1.0 + slippage / 100.0)), 'tether')
        print(path, ", ",index_token, ", ",   ",", min_out, ", ", size_delta, ", ", is_long, ", ", execution_price, ", ", )
        longboi = position_router_contract.functions.createIncreasePosition(path, index_token, amountIn,  min_out,  size_delta,  is_long, execution_price, executionFee, referral, token_contracts.arbitrum.ADDRESS_ZERO).build_transaction({'chainId': 42161, 'nonce': nonce, 'value' : executionFee,'gasPrice': Web3.to_wei('1', 'gwei')})
        send_tx(MY_PRIVATE_KEY, position_router_contract, longboi)
    elif (coin == "btc" and (collateral == "usdt" or collateral == "usdc")):
        index_token = Web3.to_checksum_address(token_contracts.arbitrum.BTC.lower())
        min_out = Web3.to_wei(amount_in / (price * (1.0 + slippage / 100.0)) * 100, 'mwei')
        amountIn = Web3.to_wei(amount_in , 'mwei')
        size_delta = Web3.to_wei(str(leverage * amount_in  *  (1.0 + slippage / 100.0)), 'tether')
        print(path, ", ",index_token, ", ",   ",", min_out, ", ", size_delta, ", ", is_long, ", ", execution_price, ", ", )
        longboi = position_router_contract.functions.createIncreasePosition(path, index_token, amountIn,  min_out,  size_delta,  is_long, execution_price, executionFee, referral, token_contracts.arbitrum.ADDRESS_ZERO).build_transaction({'chainId': 42161, 'nonce': nonce, 'value' : executionFee,'gasPrice': Web3.to_wei('1', 'gwei')})
        send_tx(MY_PRIVATE_KEY, position_router_contract, longboi)

When running script with receipt = marketLong('btc', 'usdc', 2.0, 5.50, 30000, 0.5), I get execution reverted: Router: plugin not approved

I've already called approvePluggin() on the router contract, passing in the positionRouter contract address as its argument. It should be approved unless I missed something else?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants