Skip to content

Commit

Permalink
[feat] demo js calls
Browse files Browse the repository at this point in the history
  • Loading branch information
8ball030 committed Oct 30, 2023
1 parent 645fd33 commit bedc8c7
Show file tree
Hide file tree
Showing 8 changed files with 515 additions and 10 deletions.
Empty file added lyra/constants.py
Empty file.
47 changes: 41 additions & 6 deletions lyra/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@

PUBLIC_HEADERS = {"accept": "application/json", "content-type": "application/json"}

ACTION_TYPEHASH = '0x4d7a9f27c403ff9c0f19bce61d76d82f9aa29f8d6d4b0c5474607d9770d1af17'
DOMAIN_SEPARATOR = '0xff2ba7c8d1c63329d3c2c6c9c19113440c004c51fe6413f65654962afaff00f3'
ASSET_ADDRESS = '0x8932cc48F7AD0c6c7974606cFD7bCeE2F543a124'
TRADE_MODULE_ADDRESS = '0x63Bc9D10f088eddc39A6c40Ff81E99516dfD5269'

OPTION_NAME = 'ETH-20231027-1500-P'
OPTION_SUB_ID = '644245094401698393600'


class InstrumentType(Enum):
ERC20 = "erc20"
Expand Down Expand Up @@ -46,13 +54,7 @@ class TimeInForce(Enum):
FOK = "fok"


ACTION_TYPEHASH = '0x4d7a9f27c403ff9c0f19bce61d76d82f9aa29f8d6d4b0c5474607d9770d1af17'
DOMAIN_SEPARATOR = '0xff2ba7c8d1c63329d3c2c6c9c19113440c004c51fe6413f65654962afaff00f3'
ASSET_ADDRESS = '0x8932cc48F7AD0c6c7974606cFD7bCeE2F543a124'
TRADE_MODULE_ADDRESS = '0x63Bc9D10f088eddc39A6c40Ff81E99516dfD5269'

OPTION_NAME = 'ETH-20231027-1500-P'
OPTION_SUB_ID = '644245094401698393600'


w3 = Web3()
Expand Down Expand Up @@ -197,6 +199,39 @@ def __encode_order(self, order):

return w3.keccak(encoded)

# private apis
def create_subaccount(
amount,
asset_name,
currency="USDT",
margin_type="SM",
):
"""
Create a subaccount
"""
endpoint = "create_subaccount"
url = f"{BASE_URL}/private/{endpoint}"

payload = {
"amount": "",
"asset_name": "string",
"currency": "string",
"margin_type": "PM",
"nonce": 0,
"signature": "string",
"signature_expiry_sec": 0,
"signer": "string",
"wallet": "string"
}
headers = {
"accept": "application/json",
"content-type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.text)


def main():
"""Execute the main function."""
Expand Down
158 changes: 158 additions & 0 deletions node_demo/sample.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// This is a sample script that will submit an order to the Lyra testnet

// To run this script, you must have a Lyra testnet account with some ETH and LYRA in it


const { ethers } = require("ethers");
const { WebSocket } = require('ws');
const dotenv = require('dotenv');

dotenv.config();

const PRIVATE_KEY = process.env.OWNER_PRIVATE_KEY
const PROVIDER_URL = 'https://l2-prod-testnet-0eakp60405.t.conduit.xyz';
const WS_ADDRESS = process.env.WEBSOCKET_ADDRESS || 'ws://localhost:3000/ws';
const ACTION_TYPEHASH = '0x4d7a9f27c403ff9c0f19bce61d76d82f9aa29f8d6d4b0c5474607d9770d1af17';
const DOMAIN_SEPARATOR = '0xff2ba7c8d1c63329d3c2c6c9c19113440c004c51fe6413f65654962afaff00f3';
// const ASSET_ADDRESS = '0x8932cc48F7AD0c6c7974606cFD7bCeE2F543a124';
const ASSET_ADDRESS = '0x62CF2Cc6450Dc3FbD0662Bfd69af0a4D7485Fe4E';
const TRADE_MODULE_ADDRESS = '0x63Bc9D10f088eddc39A6c40Ff81E99516dfD5269';

const PROVIDER = new ethers.JsonRpcProvider(PROVIDER_URL);
const wallet = new ethers.Wallet(PRIVATE_KEY, PROVIDER);
const encoder = ethers.AbiCoder.defaultAbiCoder();
const subaccount_id = 550

const OPTION_NAME = 'ETH-PERP'
const OPTION_SUB_ID = '0' // can retreive with public/get_instrument


async function signAuthenticationHeader() {
const timestamp = Date.now().toString();
const signature = await wallet.signMessage(timestamp);
return {
wallet: wallet.address,
timestamp: timestamp,
signature: signature,
};
}

const connectWs = async () => {
return new Promise((resolve, reject) => {
const ws = new WebSocket(WS_ADDRESS);

ws.on('open', () => {
setTimeout(() => resolve(ws), 50);
});

ws.on('error', reject);

ws.on('close', (code, reason) => {
if (code && reason.toString()) {
console.log(`WebSocket closed with code: ${code}`, `Reason: ${reason}`);
}
});
});
};

async function loginClient(wsc ) {
const login_request = JSON.stringify({
method: 'public/login',
params: await signAuthenticationHeader(),
id: Math.floor(Math.random() * 10000)
});
wsc.send(login_request);
console.log('Sent login request')
console.log(login_request)
await new Promise(resolve => setTimeout(resolve, 2000));
}

function defineOrder(){
return {
instrument_name: OPTION_NAME,
subaccount_id: subaccount_id,
direction: "buy",
limit_price: 1310,
amount: 100,
signature_expiry_sec: Math.floor(Date.now() / 1000 + 300),
max_fee: "0.01",
nonce: Number(`${Date.now()}${Math.round(Math.random() * 999)}`), // LYRA nonce format: ${CURRENT UTC MS +/- 1 day}${RANDOM 3 DIGIT NUMBER}
signer: wallet.address,
order_type: "limit",
mmp: false,
signature: "filled_in_below"
};
}

function encodeTradeData(order){
let encoded_data = encoder.encode( // same as "encoded_data" in public/order_debug
['address', 'uint', 'int', 'int', 'uint', 'uint', 'bool'],
[
ASSET_ADDRESS,
OPTION_SUB_ID,
ethers.parseUnits(order.limit_price.toString(), 18),
ethers.parseUnits(order.amount.toString(), 18),
ethers.parseUnits(order.max_fee.toString(), 18),
order.subaccount_id, order.direction === 'buy']
);
return ethers.keccak256(Buffer.from(encoded_data.slice(2), 'hex')) // same as "encoded_data_hashed" in public/order_debug
}

async function signOrder(order) {
const tradeModuleData = encodeTradeData(order)

const action_hash = ethers.keccak256(
encoder.encode(
['bytes32', 'uint256', 'uint256', 'address', 'bytes32', 'uint256', 'address', 'address'],
[
ACTION_TYPEHASH,
order.subaccount_id,
order.nonce,
TRADE_MODULE_ADDRESS,
tradeModuleData,
order.signature_expiry_sec,
wallet.address,
order.signer
]
)
); // same as "action_hash" in public/order_debug

order.signature = wallet.signingKey.sign(
ethers.keccak256(Buffer.concat([
Buffer.from("1901", "hex"),
Buffer.from(DOMAIN_SEPARATOR.slice(2), "hex"),
Buffer.from(action_hash.slice(2), "hex")
])) // same as "typed_data_hash" in public/order_debug
).serialized;
}

async function submitOrder(order, ws) {
return new Promise((resolve, reject) => {
const id = Math.floor(Math.random() * 1000);
ws.send(JSON.stringify({
method: 'private/order',
params: order,
id: id
}));

ws.on('message', (message) => {
const msg = JSON.parse(message);
if (msg.id === id) {
console.log('Got order response:', msg);
resolve(msg);
}
});
});
}

async function completeOrder() {
const ws = await connectWs();
await loginClient(ws);
const order = defineOrder();
await signOrder(order);
console.log('Submitting order:', order);
await submitOrder(order, ws);
}

completeOrder();

130 changes: 130 additions & 0 deletions node_demo/subaccount.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
const {ethers, Contract} = require("ethers");

const axios = require('axios');
const dotenv = require('dotenv');


function getUTCEpochSec() {
return Math.floor(Date.now() / 1000);
}

dotenv.config();

// Environment variables, double check these in the docs constants section
const PRIVATE_KEY = process.env.OWNER_PRIVATE_KEY
const PROVIDER_URL = 'https://l2-prod-testnet-0eakp60405.t.conduit.xyz'
const USDC_ADDRESS = '0xe80F2a02398BBf1ab2C9cc52caD1978159c215BD'
const DEPOSIT_MODULE_ADDRESS = '0xB430F3AE49f9d7a9B93fCCb558424972c385Cc38'
const CASH_ADDRESS = '0xb8a082B53BdCBFB7c44C8Baf2F924096711EADcA'
const ACTION_TYPEHASH = '0x4d7a9f27c403ff9c0f19bce61d76d82f9aa29f8d6d4b0c5474607d9770d1af17'
const STANDARD_RISK_MANAGER_ADDRESS = '0x089fde8A32CD4Ef8D9F69DAed1B4CD5aC67d1ed7'
const DOMAIN_SEPARATOR = '0xff2ba7c8d1c63329d3c2c6c9c19113440c004c51fe6413f65654962afaff00f3'

// Ethers setup
const PROVIDER = new ethers.JsonRpcProvider(PROVIDER_URL);
const wallet = new ethers.Wallet(PRIVATE_KEY, PROVIDER);
const encoder = ethers.AbiCoder.defaultAbiCoder();

const depositAmount = "10000";
const subaccountId = 0; // 0 For a new account

async function approveUSDCForDeposit(wallet, amount) {
const USDCcontract = new ethers.Contract(
USDC_ADDRESS,
["function approve(address _spender, uint256 _value) public returns (bool success)"],
wallet
);
const nonce = await wallet.provider?.getTransactionCount(wallet.address, "pending");
await USDCcontract.approve(DEPOSIT_MODULE_ADDRESS, ethers.parseUnits(amount, 6), {
gasLimit: 1000000,
nonce: nonce
});
}

function encodeDepositData(amount){
let encoded_data = encoder.encode( // same as "encoded_data" in public/create_subaccount_debug
['uint256', 'address', 'address'],
[
ethers.parseUnits(amount, 6),
CASH_ADDRESS,
STANDARD_RISK_MANAGER_ADDRESS
]
);
return ethers.keccak256(Buffer.from(encoded_data.slice(2), 'hex')) // same as "encoded_data_hashed" in public/create_subaccount_debug
}

function generateSignature(subaccountId, encodedData, expiry, nonce) {
const action_hash = ethers.keccak256( // same as "action_hash" in public/create_subaccount_debug
encoder.encode(
['bytes32', 'uint256', 'uint256', 'address', 'bytes32', 'uint256', 'address', 'address'],
[
ACTION_TYPEHASH,
subaccountId,
nonce,
DEPOSIT_MODULE_ADDRESS,
encodedData,
expiry,
wallet.address,
wallet.address
]
)
);

const typed_data_hash = ethers.keccak256( // same as "typed_data_hash" in public/create_subaccount_debug
Buffer.concat([
Buffer.from("1901", "hex"),
Buffer.from(DOMAIN_SEPARATOR.slice(2), "hex"),
Buffer.from(action_hash.slice(2), "hex"),
])
);

return wallet.signingKey.sign(typed_data_hash).serialized
}

async function signAuthenticationHeader() {
const timestamp = Date.now().toString();
const signature = await wallet.signMessage(timestamp);
return {
"X-LyraWallet": wallet.address,
"X-LyraTimestamp": timestamp,
"X-LyraSignature": signature
};
}

async function createSubaccount() {
// An action nonce is used to prevent replay attacks
// LYRA nonce format: ${CURRENT UTC MS +/- 1 day}${RANDOM 3 DIGIT NUMBER}
const nonce = Number(`${Date.now()}${Math.round(Math.random() * 999)}`);
const expiry = getUTCEpochSec() + 300; // 5 min

const encoded_data_hashed = encodeDepositData(depositAmount); // same as "encoded_data_hashed" in public/create_subaccount_debug
const depositSignature = generateSignature(subaccountId, encoded_data_hashed, expiry, nonce);
const authHeader = await signAuthenticationHeader();

await approveUSDCForDeposit(wallet, depositAmount);

try {
const response = await axios.request({
method: "POST",
url: "https://api-demo.lyra.finance/private/create_subaccount",
data: {
margin_type: "SM",
wallet: wallet.address,
signer: wallet.address,
nonce: nonce,
amount: depositAmount,
signature: depositSignature,
signature_expiry_sec: expiry,
asset_name: 'USDC',
},
headers: authHeader,
});

console.log(JSON.stringify(response.data, null, '\t'));
} catch (error) {
console.error("Error depositing to subaccount:", error);
}
}

createSubaccount();

Loading

0 comments on commit bedc8c7

Please sign in to comment.