StarkEx Perpetual Trading Playground tutorial

The StarkEx Perpetual Trading Playground is a StarkEx deployment on the Goerli testnet. You can use the Playground to learn the ropes hands-on and start developing your own Application on the Goerli testnet.

On the playground, you will be able to perform the following flows, which represent a limited collection of the Perpetual system’s full API:

  1. Deposit -
    Inserting collateral assets from a L1 wallet to a L2 position.

  2. Transfer -
    Transferring assets between two L2 positions.

  3. Trade -
    Issuing a trade between two users on L2: Trading synthetic assets, or trading collateral for synthetic assets.

  4. Withdrawal -
    Reclaiming assets from an L2 position to receive them on L1.

For a detailed breakdown of these flows, see the Gateway API in the StarkEx API Reference.

Running the Playground

The Playground is a StarkEx test environment running on the Goerli testnet. Use the curl command to issue requests to the Playground’s gateway.

Use the following URLs to interact with the test environment via the StarkEx API:

  • Gateway: https://perpetual-playground-v2.starkex.co

  • Feeder gateway: https://perpetual-playground-v2.starkex.co/feeder_gateway

Prerequisites

  • An Infura endpoint link for Goerli. If you don’t have one, please open an Ethereum account at https://infura.io/ to acquire a link.

  • A Goerli ETH account with enough ETH to pay for the deposit transaction gas fees. For your convenience, you can try this faucet to obtain Goerli ETH.

  • curl is installed.

Conducting transactions in the Playground

  1. Generate a Stark key pair using the Creating a Stark key process, detailed in the Hands-on demonstration below.

    In order to send your own transactions, you must sign transactions with a STARK signature, which requires your own Stark key pair. You can use the StarkEx Crypto SDK or the Python Crypto Library.

  2. Make a deposit to the positions you want to play with:

    1. Send a deposit transaction on L1 to the StarkEx perpetual contract.

    2. Send a deposit transaction on L2.

Now, you can perform any of the following transaction flows:

These are all explained in Hands-on demonstration, below.

For more information, on the corresponding APIs, see the StarkEx Perpetual Trading API Reference.

  • In order to get the next transaction id, you must query the StarkEx gateway by calling the get_first_unused_tx_id API, because other Playground users need to synchronize the next transaction id.

    Be aware that StarkEx does not process two transactions with the same id and does not process a transaction if the transaction with the previous id has not been processed.

  • The value for positionId that you define for your transaction can be taken by someone else.

Hands-on demonstration

The following is a hands-on guide for newcomers to the StarkEx Perpetual Trading system, which leads you step-by-step from the initial starting point of generating your own Stark key, up to the point where you issue a trade on L2. If you are familiar with the workings on the Perpetual system, or simply wish to skip this section and explore the system on your own, see General configuration.

  1. Generate a Stark key via the Key derivation process:

    In order to interact with the L2 system, a user must have a dedicated Stark key.

    The Stark key is divided into two components: the private Stark key, which is used to sign transactions the owner wishes to execute, and a public Stark key (often referred to simply as “the Stark key”) which represents the user’s address/identity on L2.

    Generate a Stark key using either the StarkEx Crypto SDK or the Python Crypto Library.

    • To generate a Stark key using the StarkEx Crypto SDK:

      This example uses a pre-generated Stark private key example, but you can generate your own by randomizing a 62 character hex-string.

      keyGen = require('./src/js/key_derivation.js')
      
      starkKey = keyGen.privateToStarkKey("3c1e9550e66958296d11b60f8e8e7a7ad990d07fa65d5f7652c4a6c87d4e3cc")
    • To generate a Stark key using the Python Crypto Library:

      from starkware.crypto.signature.signature import *
      
      private_stark_key = get_random_private_key()
      stark_key = private_key_to_ec_point_on_stark_curve(priv_key)[0]
  2. Mint collateral and approve the StarkEx contract as a spender via the: mock ERC-20 contract.

    This is a precursor to the deposit phase and will allow you to deposit the simulated collateral asset the perpetual playground supports.

    1. Navigate to the Contract tab, and under that to Write Contract.

    2. Connect your Metamask wallet so you are able to invoke the contract’s functions.

    3. Use the selfMint function to add 10000000000000 tokens to your wallet (to send the transaction via Metamask click the Write button):
      selfMint function

    4. Use the approve function to authorize StarkEx to spend your collateral on L1. Set the StarkEx contract address (0x98271F68335189DCb5bd94DE50cA3ae14DE0dc60) as the spender, and set the amount at 10000000000000:

      approve function

  3. Issue a deposit to the StarkEx contract on L1, here: https://goerli.etherscan.io/writecontract/index?m=normal&v=21.10.1.1&a=0x98271F68335189DCb5bd94DE50cA3ae14DE0dc60&p=0x1464553a75F805b9F67F73012B07B544e76d4b21&n=goerli.

    This is the first in a 2-phase deposit process which will give you access to the collateral asset on L2.

    1. Go to the *Write Contract * tab on this contract and connect your Metamask wallet as in the previous step.

    2. Issue a deposit of collateral tokens using the deposit function:

      The function receives as input:

      • Your Stark key (generated in step 1), represented as an integer.

      • The collateral asset ID on L2 (given as the integer 286442224669982855773917167725901379555005478797788066723536016706544965407)

      • A vault_id of your choosing, an integer representing a position to occupy on L2 (should be selected at random).

      • An amount to deposit, in this case 241535140800
        deposit function

  4. Send a deposit request to the StarkEx gateway (perpetual deposit documentation):

    Any user can issue a deposit to the L1 contract (as demonstrated in the previous step), thereby locking the desired amount of funds for use on L2.

    It is only when a subsequent deposit request is sent to the gateway that the deposit event on L1 is processed and (if the transaction passes the validation checks) the balance of the target position on L2 is updated accordingly.

    A deposit request to the StarkEx gateway must always be preceded by a valid L1 deposit.

    1. Get the first_unused_tx_id:

      curl https://perpetual-playground-v2.starkex.co/get_first_unused_tx_id; echo
    2. Note the response, which will serve as our tx_id in the following transaction.

      • Transactions sent to the StarkEx gateway must be serialized.

      • The above method allows us to get the next available transaction ID which we can assign to our following deposit request.

    3. Run the following cmd to send a deposit request to the StarkEx gateway:

      curl -d '{"tx_id":<tx_id>,"tx":{"position_id":"<vault_id>","public_key":"<stark_key (in hex format)>","amount":"241535140800","type":"DEPOSIT"}}' https://perpetual-playground-v2.starkex.co/add_transaction; echo

      The fields in this transaction should match those filled in the previous step (issuing a deposit to the L1 contract).

      • Note that the position_id is equal to the vault_id chosen in the previous step.

      • The tx_id is received using get_first_unused_tx_id and is then incremented for following transactions.

      • The public_key is your public Stark key, given as a hex string.

    4. Note the response from the gateway - you should see \{"code": "TRANSACTION_RECEIVED"} to indicate the gateway has received your transaction.

  5. Verify your deposit transaction is being processed. For more information, see the Feeder Gateway in the StarkEx Perpetual v2.0 REST API Reference:

    • A transaction received by the gateway isn’t guaranteed to be processed.

    • A number of validation checks are run before a transaction is included in the next batch, and failing this validations will make the Perpetual playground disregard this transaction

    • As a future-operator, you will have control over the policy regarding failed transactions.

    1. Query the feeder-gateway for the latest batch ID:

      curl https://perpetual-playground-v2.starkex.co/feeder_gateway/get_last_batch_id; echo

      This will give you the most recent batch ID that is being processed by the system, which should include your previous transaction.

    2. Use the latest batch ID to get the batch info from the feeder-gateway:

      curl https://perpetual-playground-v2.starkex.co/feeder_gateway/get_batch_info?batch_id=<latest batch_id>; echo
      • Look for your deposit in the list of recorded transactions (identified by its uniquely assigned tx_id).

      • If the transaction isn’t in the list you may try querying a previous batch ID (if all transactions in the batch you are currently reviewing have a greater tx_id than your transaction.

      • If your transaction doesn’t appear and there is a different transaction occupying the tx_id you assigned this may be an indication your transaction has failed. In this case go back to step 3 and retry.

      • If your transaction appears in the batch then this means you should now already have L2 balance in your selected position to interact with.

  6. Send some collateral to another position using a L2 transfer transaction (perpetual transfer doc):

    • Transfers in the Perpetual StarkEx system allow to move collateral (and only collateral) assets between positions.

    • For this example we will transfer collateral to some predetermined position

    1. First we need to generate a signature for our desired transfer message:

      • Using perpetual_messages.py:

        # Generate message hash to sign.
        transfer_msg_hash = get_transfer_msg(
            asset_id=
        286442224669982855773917167725901379555005478797788066723536016706544965407,
            asset_id_fee=0,
            receiver_public_key=
        2825868930652315540133093693348064822157481518754303749560050236804923333506,
            sender_position_id=<insert position_id>,
            receiver_position_id=4,
            src_fee_position_id=<insert position_id>,
            nonce=0,
            amount=3,
            max_amount_fee=0,
            expiration_timestamp=20000000)
        
        # Sign message with previously generated private key.
        r, s = sign(
            msg_hash=transfer_msg_hash,
            priv_key=priv_key)
        
        # Verify signature matches message and stark key.
        verify(
            msg_hash=transfer_msg_hash,
            r=r,
            s=s,
            public_key=stark_key)
        # Display signature components as hex.
        hex(r)
        hex(s)
      • Using perpetual_messages.js:

        # Generate message hash to sign.
        const perpetualMsgs = require('./perpetual_messages.js')
        
        msgHash = perpetualMsgs.getPerpetualTransferMessage("286442224669982855773917167725901379555005478797788066723536016706544965407"/*assetId*/, "0"/*assetIdFee*/, "2825868930652315540133093693348064822157481518754303749560050236804923333506"/*receiverPublicKey*/, "<insert position_id>"/*senderPositionId*/, "4"/*receiverPositionId*/, "<insert position_id>"/*srcFeePositionId*/, "0"/*nonce*/, "3"/*amount*/, "0"/*maxAmountFee*/, "20000000"/*expirationTimestamp*/)
        
        # Sign message with previously generated private key.
        const sigLib = require('./src/js/signature.js');
        
        sig = sigLib.sign(keyPair, msgHash)
        
        # Verify signature matches message and stark key.
        sigLib.verify(keyPair, msgHash, sig)
    2. Send an add_transaction request to the StarkEx gateway, providing our transfer message payload with the signature represented by the components r and s (remember to use an updated tx_id):

      curl -d '{"tx_id":tx_id,"tx":{"amount":"3","asset_id":"0xa21edc9d9997b1b1956f542fe95922518a9e28ace11b7b2972a1974bf5971f","expiration_timestamp":"20000000","nonce":"0","receiver_position_id":"4","receiver_public_key":"0x63f62982fa598ad7c4e469870b3a1c23316ba8fb717aa2b1ee3114283fb1b82","sender_position_id":"<your position_id>","sender_public_key":"<your public stark_key>","signature":{"r":"<your r>","s":"<your s>"}, "type":"TRANSFER"}}' https://perpetual-playground-v2.starkex.co/add_transaction; echo
    3. As with the deposit request submission, verify your transfer is being processed via the feeder gateway.

Congratulations, you have officially successfully transmitted a dedicated L2 transaction in the Perpetual Playground system. You may now use the remaining funds you deposited in the previous steps to play around freely with the system - send more transfers, execute trades, and learn by experiment!

Analyzing the output

The output includes two types:

  • On-chain deposit transactions' description of the StarkEx contract with Etherscan links.

  • Off-chain StarkEx transactions' description containing the transaction type and unique tx_id.

How do I see the on-chain state update?

How can I read the off-chain Position balances?

Position balances derive directly from the history of the transactions that were submitted to StarkEx batches. You can query this information off-chain by using the Feeder Gateway StarkEx Perpetual Trading API.

You cannot query the last batch until it has been created. So it is recommended to query the last batch only when you can track the on-chain state update.

General configuration

The playground consists of the following components:

  • Collateral asset

    Name:

    SelfService

    ERC20 Address:

    0xd44BB808bfE43095dBb94c83077766382D63952a

    Asset_id:

    '0xa21edc9d9997b1b1956f542fe95922518a9e28ace11b7b2972a1974bf5971f'

    Resolution:

    106

  • Three synthetic assets

    • '0x4254432d3130000000000000000000'

      Name:

      BTC

      Resolution:

      1010

      Risk factor:

      0.05

      External price:

      56236.782298457914212352

      Internal price:

      24153514080

    • '0x4554482d3800000000000000000000'

      Name:

      ETH

      Resolution:

      108

      Risk factor:

      0.75

      External price:

      245.752267153034510336

      Internal price:

      10554979503

    • '0x4c494e4b2d37000000000000000000'

      Name :

      LINK

      Resolution:

      107

      Risk factor:

      0.1

      External price:

      93.785511767155703808

      Internal price:

      40280570588

Collateral_asset_info:
asset_id: '0xa21edc9d9997b1b1956f542fe95922518a9e28ace11b7b2972a1974bf5971f'
  resolution: '0xf4240'
fee_position_info:
  position_id: '123456'
  public_key:
  '0x811921ddf8b35b688ede6d94a16293bddabb2362'
orders_tree_height: 64
positions_tree_height: 64
synthetic_assets_info:
  '0x4254432d3130000000000000000000':
    resolution: '0x2540be400'
    risk_factor: '214748365'
  '0x4554482d3800000000000000000000':
    resolution: '0x5f5e100'
    risk_factor: '322122548'
  '0x4c494e4b2d37000000000000000000':
    resolution: '0x989680'
    risk_factor: '429496730'

In a real StarkEx instance the general configuration includes more details regarding the oracle and funding tick.

Oracle price tick

{
   "tx_id":1,
   "tx":{
      "type":"ORACLE_PRICES_TICK",
      "oracle_prices":{
         "0x4254432d3130000000000000000000":{
            "signed_prices":{
               "0x60de32cdaf78bdab488d87f7720d5f3898f16d1461203621731f80fa85c35ff":{
                  "timestamped_signature":{
                     "timestamp":"1597468993",
                     "signature":{
                        "r":"0x7612fff3397a4b1f8ca578613c5540b46e308d948009d88a0bed9f7192997e8",
                        "s":"0x5974522f5844b7f010b0a21772ad77aef59582fb3cb2f263650e60baa844fdf"
                     }
                  },
                  "external_asset_id":"0x425443555344000000000000000000004d616b6572",
                  "price":"56236782298457914212352"
               }
            },
            "price":"24153514080"
         },
         "0x4554482d3800000000000000000000":{
            "signed_prices":{
               "0x793e9f602abba40f00411d6320287fb4d180c06c99f768d3cf91b613f3c8810":{
                  "timestamped_signature":{
                     "timestamp":"1511954808",
                     "signature":{
                        "r":"0x1ba6a1601eacfe82c195cc234d4f0e6100afb66265b8b768907aacaf63c42b3",
                        "s":"0x3c6b4e4ca06a6d733e2d83d4857bab85c06b4e52fcaf1b2264a0ca01cbf1a0e"
                     }
                  },
                  "external_asset_id":"0x455448555344000000000000000000004d616b6572",
                  "price":"245752267153034510336"
               }
            },
            "price":"10554979503"
         },
         "0x4c494e4b2d37000000000000000000":{
            "signed_prices":{
               "0x636b2c4f887da6f50dee35f21d0582b4944fe8dd1e83f61e63dabeff3e4846f":{
                  "timestamped_signature":{
                     "timestamp":"1480980993",
                     "signature":{
                        "r":"0x7f1887fba5d66e13aba37c8859531cc1f90dd59fc06479f86fab6e57ca4615c",
                        "s":"0x776545d06ae5fdf4102a07366efe43775f4977ba8d89cea9612ce6e9b7c2ca8"
                     }
                  },
                  "external_asset_id":"0x4c494e4b5553440000000000000000004d616b6572",
                  "price":"93785511767155703808"
               }
            },
            "price":"40280570588"
         }
      },
      "timestamp":"1667993252"
   }
}

Additional resources