Derive an account from the mnemonic using go-ethereum-hdwallet or other provider.
Make sure that account has ETH on Arbitrum to pay for transactions and USDC/USDT if placing buy orders.
Place an order request with our smart contracts, including permitting our contracts to spend your USDC using go-ethereum.
After to request transaction is submitted, get the order ID from the emitted OrderRequested event. Example coming soon.
Continue to listen to events emitted by the order processor (https://goethereumbook.org/en/events/) to detect fills and cancels. Query the order processor for order state. Example coming soon.
Direct Orders
This example submits a BUY order request to the OrderProcessor contract. This process involves two method calls bundled together into one transaction using the OrderProcessor's multicall utility.
The first step is to determine the fees to add to the desired order amount.
// ------------------ Configure Order ------------------// Set order amount (10 USDC)orderAmount :=new(big.Int).Mul(big.NewInt(10), big.NewInt(1e6))fmt.Println("Order Amount:", orderAmount.String())// Set buy or sell (false = buy, true = sell)sellOrder :=false// Set order type (0 = market, 1 = limit)orderType :=uint8(0)// Check the order decimals does not exceed max decimals// Applicable to sell and limit orders onlyif sellOrder || orderType ==1 {// Call the 'maxOrderDecimals' method on the contract to get max order decimalsvar maxDecimalsTxResult []interface{} maxDecimals :=new(big.Int) maxDecimalsTxResult =append(maxDecimalsTxResult, maxDecimals) err = processorContract.Call(&bind.CallOpts{}, &maxDecimalsTxResult, "maxOrderDecimals")if err !=nil { log.Fatalf("Failed to call getOrderDecimals function: %v", err) } fmt.Println("Order Decimals:", maxDecimals)// Call 'decimals' method on the asset token contract to get token decimalsvar assetTokenDecimalsTxResult []interface{} assetTokenDecimals :=new(big.Int) assetTokenDecimalsTxResult =append(assetTokenDecimalsTxResult, assetTokenDecimals) assetTokenContract := bind.NewBoundContract(common.HexToAddress(AssetToken), eip2612Abi, client, client, client) err = assetTokenContract.Call(&bind.CallOpts{}, &assetTokenDecimalsTxResult, "decimals")if err !=nil { log.Fatalf("Failed to call decimals function: %v", err) } fmt.Println("Asset Token Decimals:", assetTokenDecimals)// Calculate the allowable decimals allowableDecimals :=new(big.Int).Sub(assetTokenDecimals, maxDecimals)ifnew(big.Int).Mod(orderAmount, allowableDecimals) != big.NewInt(0) { log.Fatalf("Order amount exceeds max alloeable decimals: %v", allowableDecimals) }}// Call the 'estimateTotalFeesForOrder' method on the contract to get total feesvar feeTxResult []interface{}fees :=new(OrderFee)feeTxResult =append(feeTxResult, fees)paymentTokenAddr := common.HexToAddress(PaymentTokenAddress)err = processorContract.Call(&bind.CallOpts{}, &feeTxResult, "estimateTotalFeesForOrder", account.Address, false, paymentTokenAddr, orderAmount)
if err !=nil { log.Fatalf("Failed to call estimateTotalFeesForOrder function: %v", err)}fmt.Println("processor fees:", fees.TotalFee)// Calculate the total amount to spend considering fee ratestotalSpendAmount :=new(big.Int).Add(orderAmount, fees.TotalFee)fmt.Println("Total Spend Amount:", totalSpendAmount.String())
The first method call - selfPermit - gives the OrderProcessor permission to spend the payment token by pulling the order amount + fees from the user's account.
// ------------------ Configure Permit ------------------// Call the 'name' method on the payment token contract to get token namepaymentTokenContract := bind.NewBoundContract(paymentTokenAddr, eip2612Abi, client, client, client)var nameTxResult []interface{}name :=new(NameData)nameTxResult =append(nameTxResult, name)err = paymentTokenContract.Call(&bind.CallOpts{}, &nameTxResult, "name")if err !=nil { log.Fatalf("Failed to call name function: %v", err)}fmt.Println("Name:", name.Name)var nonceTxResult []interface{}nonce :=new(NonceData)nonceTxResult =append(nonceTxResult, nonce)err = paymentTokenContract.Call(&bind.CallOpts{}, &nonceTxResult, "nonces", account.Address)if err !=nil { log.Fatalf("Failed to call nonces function: %v", err)}fmt.Println("Nonce:", nonce.Nonce)// Fetch the current block number & its details to derive a deadline for the transactionblockNumber, err := client.BlockNumber(context.Background())if err !=nil { log.Fatalf("Failed to get block number: %v", err)}block, err := client.BlockByNumber(context.Background(), big.NewInt(int64(blockNumber)))if err !=nil { log.Fatalf("Failed to get block details: %v", err)}deadline := block.Time() +300deadlineBigInt :=new(big.Int).SetUint64(deadline)fmt.Println("Deadline:", deadline)// Create the domain struct based on EIP712 requirementsdomainStruct :=apitypes.TypedDataDomain{ Name: name.Name, Version: "1", // Version may be different in the wild ChainId: math.NewHexOrDecimal256(chainID.Int64()), VerifyingContract: PaymentTokenAddress,}// Create the message struct for hashingpermitDataStruct :=map[string]interface{}{"owner": account.Address.String(),"spender": processorAddress.String(),"value": totalSpendAmount,"nonce": nonce.Nonce,"deadline": deadlineBigInt,}// Define the DataTypes using previously defined types and the constructed structsDataTypes :=apitypes.TypedData{ Types: permitTypes, // This should be defined elsewhere (or passed as an argument if dynamic) PrimaryType: "Permit", Domain: domainStruct, Message: permitDataStruct,}// Hash the domain structdomainHash, err := DataTypes.HashStruct("EIP712Domain", domainStruct.Map())if err !=nil { log.Fatalf("Failed to create EIP-712 domain hash: %v", err)}fmt.Println("Domain Separator: ", domainHash.String())// Hash the message structpermitTypeHash := DataTypes.TypeHash("Permit")fmt.Println("Permit Type Hash: ", permitTypeHash.String())messageHash, err := DataTypes.HashStruct("Permit", permitDataStruct)if err !=nil { log.Fatalf("Failed to create EIP-712 hash: %v", err)}fmt.Println("Permit Message Hash: ", messageHash.String())// Combine and hash the domain and message hashes according to EIP-712typedHash := crypto.Keccak256(append([]byte("\x19\x01"), append(domainHash, messageHash...)...))// Sign the hash and construct R, S and V components of the signaturesignature, err := crypto.Sign(typedHash, privateKey)if err !=nil { log.Fatalf("Failed to sign EIP-712 hash: %v", err)}if signature[64] <27 { signature[64] +=27}fmt.Printf("EIP-712 Signature: 0x%x\n", hex.EncodeToString(signature))r := signature[:32]s := signature[32:64]v := signature[64]fmt.Printf("R: 0x%x\n", r)fmt.Printf("S: 0x%x\n", s)fmt.Printf("V: %d\n", v)// Constructing function data for selfPermitvar rArray, sArray [32]bytecopy(rArray[:], r)copy(sArray[:], s)selfPermitData, err := processorAbi.Pack("selfPermit", paymentTokenAddr, account.Address, totalSpendAmount, deadlineBigInt, v, rArray, sArray,)if err !=nil { log.Fatalf("Failed to encode selfPermit function data: %v", err)}
The second method call - requestOrder - submits the order request to be filled by the protocol. This is submitted with the spending permit.
// ------------------ Submit Order ------------------// Constructing function data for requestOrderorder :=OrderStruct{ Recipient: account.Address, AssetToken: common.HexToAddress(AssetToken), PaymentToken: paymentTokenAddr, Sell: sellOrder, OrderType: orderType, AssetTokenQuantity: big.NewInt(0), PaymentTokenQuantity: orderAmount, Price: big.NewInt(0), Tif: 1, SplitRecipient: common.BigToAddress(big.NewInt(0)), SplitAmount: big.NewInt(0),}requestOrderData, err := processorAbi.Pack("requestOrder", order,)if err !=nil { log.Fatalf("Failed to encode requestOrder function data: %v", err)}// Multicall - executing multiple transactions in one callmulticallArgs := [][]byte{ selfPermitData, requestOrderData,}// Estimate gas limit for the transactiongasPrice, err := client.SuggestGasPrice(context.Background())if err !=nil { log.Fatalf("Failed to suggest gas price: %v", err)}opts :=&bind.TransactOpts{ From: account.Address, Signer: signer.Signer, GasLimit: 6721975, // Could be replaced with EstimateGas GasPrice: gasPrice, Value: big.NewInt(0),}// Submitting the transaction and waiting for it to be minedtx, err := processorContract.Transact(opts, "multicall", multicallArgs)if err !=nil { log.Fatalf("Failed to submit multicall transaction: %v", err)}// Verifying transaction status and printing resultreceipt, err := bind.WaitMined(context.Background(), client, tx)if err !=nil { log.Fatalf("Failed to get transaction receipt: %v", err)}if receipt.Status ==0 { log.Fatalf("Transaction failed with hash: %s\n", tx.Hash().Hex())} else { fmt.Printf("Transaction successful with hash: %s\n", tx.Hash().Hex())}
Once the transaction is mined, the event logs from resulting transaction receipt can be unpacked to obtain the order recipient's order index. The recipient's order index can be used to query the BuyProcessor for the current state of the order or look up the event history for that order.