added PMM

This commit is contained in:
KS
2024-03-10 20:28:00 +03:00
parent 184f66bf17
commit 6f5b537c47
13 changed files with 408 additions and 10 deletions

View File

@@ -0,0 +1,63 @@
export const orionRFQContractABI =
[
{
"inputs": [
{
"components": [
{
"internalType": "uint256",
"name": "info",
"type": "uint256"
},
{
"internalType": "address",
"name": "makerAsset",
"type": "address"
},
{
"internalType": "address",
"name": "takerAsset",
"type": "address"
},
{
"internalType": "address",
"name": "maker",
"type": "address"
},
{
"internalType": "address",
"name": "allowedSender",
"type": "address"
},
{
"internalType": "uint256",
"name": "makingAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "takingAmount",
"type": "uint256"
}
],
"internalType": "struct OrderRFQLib.OrderRFQ",
"name": "order",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "signature",
"type": "bytes"
},
{
"internalType": "uint256",
"name": "flagsAndAmount",
"type": "uint256"
}
],
"name": "fillOrderRFQ",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
];

78
src/Unit/Pmm/index.ts Normal file
View File

@@ -0,0 +1,78 @@
import type Unit from '../index';
import { z } from 'zod';
import {pmmOrderSchema} from "./schemas/order";
import {simpleFetch} from "simple-typed-fetch";
import {ethers, Wallet} from "ethers";
import {BigNumber} from "bignumber.js";
import { ERC20__factory } from '@orionprotocol/contracts/lib/ethers-v6/index.js';
import {orionRFQContractABI} from "./abi/OrionRFQ";
export default class Pmm {
private readonly unit: Unit;
private readonly provider: ethers.Provider;
private contractAddress: string;
constructor(unit: Unit) {
this.unit = unit;
this.provider = unit.provider;
this.contractAddress = '';
}
private isInitialized() : boolean {
return this.contractAddress !== '';
}
public async init() {
if(this.isInitialized())
return;
const { orionPMMRouterContractAddress } = await simpleFetch(this.unit.blockchainService.getPmmInfo)();
this.contractAddress = orionPMMRouterContractAddress;
}
public async setAllowance(token: string, amount: string, signer: Wallet) {
await this.init();
const bnTargetAmount = new BigNumber(amount);
const walletAddress = await signer.getAddress();
const tokenContract = ERC20__factory
.connect(token, this.unit.provider);
const unsignedApproveTx = await tokenContract
.approve.populateTransaction(
this.contractAddress,
bnTargetAmount.toString()
);
const nonce = await this.provider.getTransactionCount(walletAddress, 'pending');
const { gasPrice, maxFeePerGas } = await this.provider.getFeeData();
const network = await this.provider.getNetwork();
if (gasPrice !== null)
unsignedApproveTx.gasPrice = gasPrice;
if(maxFeePerGas !== null)
unsignedApproveTx.maxFeePerGas = maxFeePerGas;
unsignedApproveTx.chainId = network.chainId;
unsignedApproveTx.nonce = nonce;
unsignedApproveTx.from = walletAddress;
const gasLimit = await this.provider.estimateGas(unsignedApproveTx);
unsignedApproveTx.gasLimit = gasLimit;
const signedTx = await signer.signTransaction(unsignedApproveTx);
const txResponse = await this.provider.broadcastTransaction(signedTx);
await txResponse.wait();
}
public async FillRFQOrder(order : z.infer<typeof pmmOrderSchema>, signer: Wallet) {
await this.init();
if(!order.success)
throw Error("Invalid order provided");
const contract = new ethers.Contract(this.contractAddress, orionRFQContractABI, signer);
// @ts-ignore
return contract.fillOrderRFQ(order.quotation, order.signature, BigInt(0));
}
}

View File

@@ -0,0 +1,18 @@
import {z} from "zod";
export const pmmOrderQuotationSchema = z.object({
info: z.string().default(''),
makerAsset: z.string().default(''),
takerAsset: z.string().default(''),
maker: z.string().default(''),
allowedSender: z.string().default(''),
makingAmount: z.string().default(''),
takingAmount: z.string().default(''),
});
export const pmmOrderSchema = z.object({
quotation: pmmOrderQuotationSchema.default({}),
signature: z.string().default(''),
success: z.boolean().default(false),
error: z.string().default(''),
});

View File

@@ -11,6 +11,7 @@ import Exchange from './Exchange/index.js';
import { chains, envs } from '../config';
import type { networkCodes } from '../constants/index.js';
import { IndexerService } from '../services/Indexer';
import Pmm from "./Pmm";
type KnownConfig = {
env: KnownEnv
@@ -30,6 +31,8 @@ export default class Unit {
public readonly aggregator: Aggregator;
public readonly pmm: Pmm;
public readonly priceFeed: PriceFeed;
public readonly exchange: Exchange;
@@ -122,5 +125,6 @@ export default class Unit {
this.config.basicAuth
);
this.exchange = new Exchange(this);
this.pmm = new Pmm(this);
}
}

View File

@@ -19,6 +19,9 @@ import httpToWS from '../../utils/httpToWS.js';
import { ethers } from 'ethers';
import orderSchema from './schemas/orderSchema.js';
import { fetchWithValidation } from 'simple-typed-fetch';
import hmacSHA256 from "crypto-js/hmac-sha256";
import Hex from "crypto-js/enc-hex";
import {pmmOrderSchema} from "../../Unit/Pmm/schemas/order";
class Aggregator {
private readonly apiUrl: string;
@@ -369,6 +372,106 @@ class Aggregator {
url.searchParams.append('limit', limit.toString());
return fetchWithValidation(url.toString(), atomicSwapHistorySchema, { headers: this.basicAuthHeaders });
};
private encode_utf8(s : string) {
return unescape(encodeURIComponent(s));
}
private sign(message : string, key: string) {
return hmacSHA256(
this.encode_utf8(message),
this.encode_utf8(key)
).toString(Hex);
}
private generateHeaders(body : any, method : string, path : string, timestamp : number, apiKey : string, secretKey : string) {
const sortedBody = Object.keys(body)
.sort()
.map((key) => (
`${key}=${body[key]}`
)).join('&');
const payload = timestamp + method.toUpperCase() + path + sortedBody;
const signature = this.sign(payload, secretKey);
const httpOptions = {
headers: {
'API-KEY': apiKey,
'ACCESS-TIMESTAMP': timestamp.toString(),
'ACCESS-SIGN': signature
}
};
return httpOptions;
}
public async RFQOrder(
tokenFrom: string,
tokenTo: string,
fromTokenAmount: string,
apiKey: string, //
secretKey: string,
wallet: string
) : Promise<z.infer<typeof pmmOrderSchema>> {
// Making the order structure
const
path = '/rfq'
, url = `${this.apiUrl}/api/v1/integration/pmm`+path
, headers = {
'Content-Type': 'application/json',
}
, data = {
"baseToken":tokenFrom, // USDT
"quoteToken":tokenTo, // ORN
"amount": fromTokenAmount, // 100
"taker": wallet,
"feeBps": 0
}
, method = 'POST'
, timestamp = Date.now()
, signatureHeaders = this.generateHeaders(data, method, path, timestamp, apiKey, secretKey)
, compiledHeaders = {...headers, ...signatureHeaders.headers, }
, body = JSON.stringify(data)
;
let res = pmmOrderSchema.parse({});
try {
const result = await fetch(url,{
headers: compiledHeaders,
method,
body
});
const json = await result.json();
const parseResult = pmmOrderSchema.safeParse(json);
if(!parseResult.success) {
// Try to parse error answer
const errorSchema = z.object({error: z.object({code: z.number(), reason: z.string()})});
const errorParseResult = errorSchema.safeParse(json);
if(!errorParseResult.success)
throw Error(`Unrecognized answer from aggregator: ${json}`);
throw Error(errorParseResult.data.error.reason);
}
res.quotation = parseResult.data.quotation;
res.signature = parseResult.data.signature;
res.error = '';
res.success = true;
// return result;
}
catch(err) {
res.error = `${err}`;
}
return res;
}
}
export * as schemas from './schemas/index.js';
export * as ws from './ws/index.js';

View File

@@ -12,6 +12,7 @@ import {
pairStatusSchema,
pricesWithQuoteAssetSchema,
referralDataSchema,
pmmSchema
} from './schemas/index.js';
import type redeemOrderSchema from '../Aggregator/schemas/redeemOrderSchema.js';
import { sourceAtomicHistorySchema, targetAtomicHistorySchema } from './schemas/atomicHistorySchema.js';
@@ -82,6 +83,7 @@ class BlockchainService {
this.getAuthToken = this.getAuthToken.bind(this);
this.getCirculatingSupply = this.getCirculatingSupply.bind(this);
this.getInfo = this.getInfo.bind(this);
this.getPmmInfo = this.getPmmInfo.bind(this);
this.getPoolsConfig = this.getPoolsConfig.bind(this);
this.getPoolsInfo = this.getPoolsInfo.bind(this);
this.getPoolsLpAndStaked = this.getPoolsLpAndStaked.bind(this);
@@ -176,6 +178,8 @@ class BlockchainService {
getInfo = () => fetchWithValidation(`${this.apiUrl}/api/info`, infoSchema);
getPmmInfo = () => fetchWithValidation(`${this.apiUrl}/api/pmm-info`, pmmSchema);
getPoolsConfig = () => fetchWithValidation(
`${this.apiUrl}/api/pools/config`,
poolsConfigSchema,

View File

@@ -13,5 +13,6 @@ export { default as poolsLpAndStakedSchema } from './poolsLpAndStakedSchema.js';
export { default as userVotesSchema } from './userVotesSchema.js';
export { default as userEarnedSchema } from './userEarnedSchema.js';
export { default as poolsV3InfoSchema } from './poolsV3InfoSchema.js';
export { default as pmmSchema } from './pmmSchema.js';
export { pricesWithQuoteAssetSchema } from './pricesWithQuoteAssetSchema.js';
export { referralDataSchema } from './referralDataSchema.js';

View File

@@ -0,0 +1,7 @@
import { z } from 'zod';
const pmmSchema = z.object({
orionPMMRouterContractAddress: z.string()
});
export default pmmSchema