feat(express_relay): Sample protocol and monitor (#1374)

This commit is contained in:
Amin Moghaddam 2024-03-22 11:57:38 +01:00 committed by GitHub
parent 7796fbe025
commit 0735cdb975
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 1516 additions and 0 deletions

View File

@ -0,0 +1,6 @@
module.exports = {
root: true,
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
};

View File

@ -0,0 +1,5 @@
lib/*
out
cache
tslib
!lib/README.md

View File

@ -0,0 +1,20 @@
# EasyLend Protocol
EasyLend is a simplified lending protocol that uses Express Relay for avoiding value leakage on liquidations.
It uses Pyth price feeds to calculate the asset values and the liquidation thresholds.
This project illustrates how to use the Express Relay SDK for contract integration and publishing opportunities.
## Contracts
The contracts are located in the `contracts` directory. The `EasyLend.sol` file contains the main contract logic.
The protocol can allow creation of undercollateralized vaults that are liquidatable upon creation. This is solely
for ease of testing and demonstration purposes.
## Monitoring script
The script in `src/monitor.ts` is used to monitor the vaults health and publish the liquidation opportunities:
- It subscribes to Pyth price feeds to get the latest prices for the assets used in the protocol.
- It periodically checks for new vaults using the chain rpc.
- Upon finding a vault that is below the liquidation threshold, it publishes a liquidation opportunity using the Express Relay SDK.

View File

@ -0,0 +1,358 @@
// Copyright (C) 2024 Lavra Holdings Limited - All Rights Reserved
pragma solidity ^0.8.13;
import "./EasyLendStructs.sol";
import "./EasyLendErrors.sol";
import "forge-std/StdMath.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
import "@pythnetwork/express-relay-sdk-solidity/IExpressRelayFeeReceiver.sol";
import "@pythnetwork/express-relay-sdk-solidity/IExpressRelay.sol";
contract EasyLend is IExpressRelayFeeReceiver {
using SafeERC20 for IERC20;
event VaultReceivedETH(address sender, uint256 amount, bytes permissionKey);
uint256 _nVaults;
address public immutable expressRelay;
mapping(uint256 => Vault) _vaults;
address _oracle;
bool _allowUndercollateralized;
/**
* @notice EasyLend constructor - Initializes a new token vault contract with given parameters
*
* @param expressRelayAddress: address of the express relay
* @param oracleAddress: address of the oracle contract
* @param allowUndercollateralized: boolean to allow undercollateralized vaults to be created and updated. Can be set to true for testing.
*/
constructor(
address expressRelayAddress,
address oracleAddress,
bool allowUndercollateralized
) {
_nVaults = 0;
expressRelay = expressRelayAddress;
_oracle = oracleAddress;
_allowUndercollateralized = allowUndercollateralized;
}
/**
* @notice getLastVaultId function - getter function to get the id of the next vault to be created
* Ids are sequential and start from 0
*/
function getLastVaultId() public view returns (uint256) {
return _nVaults;
}
/**
* @notice convertToUint function - converts a Pyth price struct to a uint256 representing the price of an asset
*
* @param price: Pyth price struct to be converted
* @param targetDecimals: target number of decimals for the output
*/
function convertToUint(
PythStructs.Price memory price,
uint8 targetDecimals
) private pure returns (uint256) {
if (price.price < 0 || price.expo > 0 || price.expo < -255) {
revert InvalidPriceExponent();
}
uint8 priceDecimals = uint8(uint32(-1 * price.expo));
if (targetDecimals >= priceDecimals) {
return
uint(uint64(price.price)) *
10 ** uint32(targetDecimals - priceDecimals);
} else {
return
uint(uint64(price.price)) /
10 ** uint32(priceDecimals - targetDecimals);
}
}
/**
* @notice getPrice function - retrieves price of a given token from the oracle
*
* @param id: price feed Id of the token
*/
function _getPrice(bytes32 id) internal view returns (uint256) {
IPyth oracle = IPyth(payable(_oracle));
return convertToUint(oracle.getPrice(id), 18);
}
function getAllowUndercollateralized() public view returns (bool) {
return _allowUndercollateralized;
}
function getOracle() public view returns (address) {
return _oracle;
}
/**
* @notice getVaultHealth function - calculates vault collateral/debt ratio
*
* @param vaultId: Id of the vault for which to calculate health
*/
function getVaultHealth(uint256 vaultId) public view returns (uint256) {
Vault memory vault = _vaults[vaultId];
return _getVaultHealth(vault);
}
/**
* @notice _getVaultHealth function - calculates vault collateral/debt ratio using the on-chain price feeds.
* In a real world scenario, caller should ensure that the price feeds are up to date before calling this function.
*
* @param vault: vault struct containing vault parameters
*/
function _getVaultHealth(
Vault memory vault
) internal view returns (uint256) {
uint256 priceCollateral = _getPrice(vault.tokenIdCollateral);
uint256 priceDebt = _getPrice(vault.tokenIdDebt);
if (priceCollateral < 0) {
revert NegativePrice();
}
if (priceDebt < 0) {
revert NegativePrice();
}
uint256 valueCollateral = priceCollateral * vault.amountCollateral;
uint256 valueDebt = priceDebt * vault.amountDebt;
return (valueCollateral * 1_000_000_000_000_000_000) / valueDebt;
}
/**
* @notice createVault function - creates a vault
*
* @param tokenCollateral: address of the collateral token of the vault
* @param tokenDebt: address of the debt token of the vault
* @param amountCollateral: amount of collateral tokens in the vault
* @param amountDebt: amount of debt tokens in the vault
* @param minHealthRatio: minimum health ratio of the vault, 10**18 is 100%
* @param minPermissionlessHealthRatio: minimum health ratio of the vault before permissionless liquidations are allowed. This should be less than minHealthRatio
* @param tokenIdCollateral: price feed Id of the collateral token
* @param tokenIdDebt: price feed Id of the debt token
* @param updateData: data to update price feeds with
*/
function createVault(
address tokenCollateral,
address tokenDebt,
uint256 amountCollateral,
uint256 amountDebt,
uint256 minHealthRatio,
uint256 minPermissionlessHealthRatio,
bytes32 tokenIdCollateral,
bytes32 tokenIdDebt,
bytes[] calldata updateData
) public payable returns (uint256) {
_updatePriceFeeds(updateData);
Vault memory vault = Vault(
tokenCollateral,
tokenDebt,
amountCollateral,
amountDebt,
minHealthRatio,
minPermissionlessHealthRatio,
tokenIdCollateral,
tokenIdDebt
);
if (minPermissionlessHealthRatio > minHealthRatio) {
revert InvalidHealthRatios();
}
if (
!_allowUndercollateralized &&
_getVaultHealth(vault) < vault.minHealthRatio
) {
revert UncollateralizedVaultCreation();
}
IERC20(vault.tokenCollateral).safeTransferFrom(
msg.sender,
address(this),
vault.amountCollateral
);
IERC20(vault.tokenDebt).safeTransfer(msg.sender, vault.amountDebt);
_vaults[_nVaults] = vault;
_nVaults += 1;
return _nVaults;
}
/**
* @notice updateVault function - updates a vault's collateral and debt amounts
*
* @param vaultId: Id of the vault to be updated
* @param deltaCollateral: delta change to collateral amount (+ means adding collateral tokens, - means removing collateral tokens)
* @param deltaDebt: delta change to debt amount (+ means withdrawing debt tokens from protocol, - means resending debt tokens to protocol)
*/
function updateVault(
uint256 vaultId,
int256 deltaCollateral,
int256 deltaDebt
) public {
Vault memory vault = _vaults[vaultId];
uint256 qCollateral = stdMath.abs(deltaCollateral);
uint256 qDebt = stdMath.abs(deltaDebt);
bool withdrawExcessiveCollateral = (deltaCollateral < 0) &&
(qCollateral > vault.amountCollateral);
if (withdrawExcessiveCollateral) {
revert InvalidVaultUpdate();
}
uint256 futureCollateral = (deltaCollateral >= 0)
? (vault.amountCollateral + qCollateral)
: (vault.amountCollateral - qCollateral);
uint256 futureDebt = (deltaDebt >= 0)
? (vault.amountDebt + qDebt)
: (vault.amountDebt - qDebt);
vault.amountCollateral = futureCollateral;
vault.amountDebt = futureDebt;
if (
!_allowUndercollateralized &&
_getVaultHealth(vault) < vault.minHealthRatio
) {
revert InvalidVaultUpdate();
}
// update collateral position
if (deltaCollateral >= 0) {
// sender adds more collateral to their vault
IERC20(vault.tokenCollateral).safeTransferFrom(
msg.sender,
address(this),
qCollateral
);
_vaults[vaultId].amountCollateral += qCollateral;
} else {
// sender takes back collateral from their vault
IERC20(vault.tokenCollateral).safeTransfer(msg.sender, qCollateral);
_vaults[vaultId].amountCollateral -= qCollateral;
}
// update debt position
if (deltaDebt >= 0) {
// sender takes out more debt position
IERC20(vault.tokenDebt).safeTransfer(msg.sender, qDebt);
_vaults[vaultId].amountDebt += qDebt;
} else {
// sender sends back debt tokens
IERC20(vault.tokenDebt).safeTransferFrom(
msg.sender,
address(this),
qDebt
);
_vaults[vaultId].amountDebt -= qDebt;
}
}
/**
* @notice getVault function - getter function to get a vault's parameters
*
* @param vaultId: Id of the vault
*/
function getVault(uint256 vaultId) public view returns (Vault memory) {
return _vaults[vaultId];
}
/**
* @notice _updatePriceFeeds function - updates the specified price feeds with given data
*
* @param updateData: data to update price feeds with
*/
function _updatePriceFeeds(bytes[] calldata updateData) internal {
if (updateData.length == 0) {
return;
}
IPyth oracle = IPyth(payable(_oracle));
oracle.updatePriceFeeds{value: msg.value}(updateData);
}
/**
* @notice liquidate function - liquidates a vault
* This function calculates the health of the vault and based on the vault parameters one of the following actions is taken:
* 1. If health >= minHealthRatio, don't liquidate
* 2. If minHealthRatio > health >= minPermissionlessHealthRatio, only liquidate if the vault is permissioned via express relay
* 3. If minPermissionlessHealthRatio > health, liquidate no matter what
*
* @param vaultId: Id of the vault to be liquidated
*/
function liquidate(uint256 vaultId) public {
Vault memory vault = _vaults[vaultId];
uint256 vaultHealth = _getVaultHealth(vault);
// if vault health is above the minimum health ratio, don't liquidate
if (vaultHealth >= vault.minHealthRatio) {
revert InvalidLiquidation();
}
if (vaultHealth >= vault.minPermissionlessHealthRatio) {
// if vault health is below the minimum health ratio but above the minimum permissionless health ratio,
// only liquidate if permissioned
if (
!IExpressRelay(expressRelay).isPermissioned(
address(this), // protocol fee receiver
abi.encode(vaultId) // vault id uniquely represents the opportunity and can be used as permission id
)
) {
revert InvalidLiquidation();
}
}
IERC20(vault.tokenDebt).transferFrom(
msg.sender,
address(this),
vault.amountDebt
);
IERC20(vault.tokenCollateral).transfer(
msg.sender,
vault.amountCollateral
);
_vaults[vaultId].amountCollateral = 0;
_vaults[vaultId].amountDebt = 0;
}
/**
* @notice liquidateWithPriceUpdate function - liquidates a vault after updating the specified price feeds with given data
*
* @param vaultId: Id of the vault to be liquidated
* @param updateData: data to update price feeds with
*/
function liquidateWithPriceUpdate(
uint256 vaultId,
bytes[] calldata updateData
) external payable {
_updatePriceFeeds(updateData);
liquidate(vaultId);
}
/**
* @notice receiveAuctionProceedings function - receives native token from the express relay
* You can use permission key to distribute the received funds to users who got liquidated, LPs, etc...
*
* @param permissionKey: permission key that was used for the auction
*/
function receiveAuctionProceedings(
bytes calldata permissionKey
) external payable {
emit VaultReceivedETH(msg.sender, msg.value, permissionKey);
}
receive() external payable {}
}

View File

@ -0,0 +1,20 @@
// Copyright (C) 2024 Lavra Holdings Limited - All Rights Reserved
pragma solidity ^0.8.13;
// Signature: 0xe922edfd
error UncollateralizedVaultCreation();
// Signature: 0xdcb430ee
error InvalidVaultUpdate();
// Signature: 0x9cd7b1c6
error InvalidPriceExponent();
// Signature: 0x85914873
error InvalidLiquidation();
// Signature: 0x61ca76d2
error NegativePrice();
// Signature: 0x4a7a3163
error InvalidHealthRatios();

View File

@ -0,0 +1,13 @@
// Copyright (C) 2024 Lavra Holdings Limited - All Rights Reserved
pragma solidity ^0.8.13;
struct Vault {
address tokenCollateral;
address tokenDebt;
uint256 amountCollateral;
uint256 amountDebt;
uint256 minHealthRatio; // 10**18 is 100%
uint256 minPermissionlessHealthRatio;
bytes32 tokenIdCollateral;
bytes32 tokenIdDebt;
}

View File

@ -0,0 +1,10 @@
[profile.default]
src = "contracts"
out = "out"
libs = [
'lib',
'../../../node_modules',
'../../../target_chains/ethereum/sdk/solidity'
]
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options

View File

@ -0,0 +1 @@
Forge installs the dependencies in this folder. They are .gitignored

View File

@ -0,0 +1,40 @@
{
"name": "easylend",
"version": "0.1.0",
"description": "Example lending protocol with express relay integration",
"private": true,
"files": [
"tslib/**/*"
],
"scripts": {
"build": "tsc",
"lint": "eslint src/",
"format": "prettier --write \"src/**/*.ts\"",
"monitor": "npm run build && node tslib/monitor.js",
"install-forge-deps": "forge install foundry-rs/forge-std@v1.7.6 --no-git --no-commit"
},
"author": "",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/pyth-network/pyth-crosschain.git"
},
"dependencies": {
"@openzeppelin/contracts": "^4.5.0",
"@pythnetwork/express-relay-evm-js": "*",
"@pythnetwork/express-relay-sdk-solidity": "*",
"@pythnetwork/pyth-evm-js": "*",
"@pythnetwork/pyth-sdk-solidity": "*",
"ts-node": "^10.9.1",
"typescript": "^5.3.3",
"viem": "^2.7.6"
},
"devDependencies": {
"@types/yargs": "^17.0.10",
"eslint": "^8.56.0",
"prettier": "^2.6.2",
"typedoc": "^0.25.7",
"typescript": "^5.1",
"yargs": "^17.4.1"
}
}

View File

@ -0,0 +1,3 @@
forge-std/=lib/forge-std/src/
@openzeppelin/=../../../node_modules/@openzeppelin/
@pythnetwork/=../../../node_modules/@pythnetwork/

View File

@ -0,0 +1,163 @@
// This is only a subset of the generated abi necessary for the monitor script
export const abi = [
{
type: "function",
name: "getLastVaultId",
inputs: [],
outputs: [
{
name: "",
type: "uint256",
internalType: "uint256",
},
],
stateMutability: "view",
},
{
type: "function",
name: "getVault",
inputs: [
{
name: "vaultId",
type: "uint256",
internalType: "uint256",
},
],
outputs: [
{
name: "",
type: "tuple",
internalType: "struct Vault",
components: [
{
name: "tokenCollateral",
type: "address",
internalType: "address",
},
{
name: "tokenDebt",
type: "address",
internalType: "address",
},
{
name: "amountCollateral",
type: "uint256",
internalType: "uint256",
},
{
name: "amountDebt",
type: "uint256",
internalType: "uint256",
},
{
name: "minHealthRatio",
type: "uint256",
internalType: "uint256",
},
{
name: "minPermissionLessHealthRatio",
type: "uint256",
internalType: "uint256",
},
{
name: "tokenIdCollateral",
type: "bytes32",
internalType: "bytes32",
},
{
name: "tokenIdDebt",
type: "bytes32",
internalType: "bytes32",
},
],
},
],
stateMutability: "view",
},
{
type: "function",
name: "liquidate",
inputs: [
{
name: "vaultId",
type: "uint256",
internalType: "uint256",
},
],
outputs: [],
stateMutability: "nonpayable",
},
{
type: "function",
name: "liquidateWithPriceUpdate",
inputs: [
{
name: "vaultId",
type: "uint256",
internalType: "uint256",
},
{
name: "updateData",
type: "bytes[]",
internalType: "bytes[]",
},
],
outputs: [],
stateMutability: "payable",
},
{
type: "event",
name: "VaultReceivedETH",
inputs: [
{
name: "sender",
type: "address",
indexed: false,
internalType: "address",
},
{
name: "amount",
type: "uint256",
indexed: false,
internalType: "uint256",
},
{
name: "permissionKey",
type: "bytes",
indexed: false,
internalType: "bytes",
},
],
anonymous: false,
},
{
type: "error",
name: "InvalidHealthRatios",
inputs: [],
},
{
type: "error",
name: "InvalidLiquidation",
inputs: [],
},
{
type: "error",
name: "InvalidPriceExponent",
inputs: [],
},
{
type: "error",
name: "InvalidVaultUpdate",
inputs: [],
},
{
type: "error",
name: "NegativePrice",
inputs: [],
},
{
type: "error",
name: "UncollateralizedVaultCreation",
inputs: [],
},
] as const;

View File

@ -0,0 +1,239 @@
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import {
checkAddress,
Client,
OpportunityParams,
} from "@pythnetwork/express-relay-evm-js";
import { privateKeyToAccount } from "viem/accounts";
import type { ContractFunctionReturnType } from "viem";
import {
Address,
createPublicClient,
encodeAbiParameters,
encodeFunctionData,
getContract,
Hex,
http,
isHex,
} from "viem";
import { optimismSepolia } from "viem/chains";
import { abi } from "./abi";
import {
PriceFeed,
PriceServiceConnection,
} from "@pythnetwork/price-service-client";
type VaultWithId = ContractFunctionReturnType<
typeof abi,
"view",
"getVault"
> & { id: bigint };
class ProtocolMonitor {
private client: Client;
private subscribedIds: Set<string> = new Set();
private prices: Record<Hex, PriceFeed> = {};
private priceConnection: PriceServiceConnection;
constructor(
expressRelayEndpoint: string,
pythEndpoint: string,
private chainId: string,
private wethContract: Address,
private vaultContract: Address,
private onlyRecent: number | undefined
) {
this.client = new Client({ baseUrl: expressRelayEndpoint });
this.priceConnection = new PriceServiceConnection(pythEndpoint, {
priceFeedRequestConfig: { binary: true },
});
}
updatePrice(feed: PriceFeed) {
this.prices[`0x${feed.id}`] = feed;
}
async subscribeToPriceFeed(tokenId: string) {
if (!this.subscribedIds.has(tokenId)) {
await this.priceConnection.subscribePriceFeedUpdates(
[tokenId],
this.updatePrice.bind(this)
);
this.subscribedIds.add(tokenId);
}
}
async checkVaults() {
const rpcClient = createPublicClient({
chain: optimismSepolia,
transport: http(),
});
const contract = getContract({
address: this.vaultContract,
abi,
client: rpcClient,
});
const lastVaultId = await contract.read.getLastVaultId();
const vaults: VaultWithId[] = [];
let startVaultId = 0n;
if (this.onlyRecent && lastVaultId > BigInt(this.onlyRecent)) {
startVaultId = lastVaultId - BigInt(this.onlyRecent);
}
for (let vaultId = startVaultId; vaultId < lastVaultId; vaultId++) {
const vault = await contract.read.getVault([vaultId]);
// Already liquidated vault
if (vault.amountCollateral == 0n && vault.amountDebt == 0n) {
continue;
}
vaults.push({ id: vaultId, ...vault });
await this.subscribeToPriceFeed(vault.tokenIdCollateral);
await this.subscribeToPriceFeed(vault.tokenIdDebt);
}
for (const vault of vaults) {
if (this.isLiquidatable(vault)) {
const opportunity = this.createOpportunity(vault);
await this.client.submitOpportunity(opportunity);
}
}
}
async start() {
// eslint-disable-next-line no-constant-condition
while (true) {
await this.checkVaults();
await new Promise((resolve) => setTimeout(resolve, 10000));
}
}
private createOpportunity(vault: VaultWithId) {
const priceUpdates = [
this.prices[vault.tokenIdCollateral].getVAA()!,
this.prices[vault.tokenIdDebt].getVAA()!,
];
const vaas: Hex[] = priceUpdates.map(
(vaa): Hex => `0x${Buffer.from(vaa, "base64").toString("hex")}`
);
const calldata = encodeFunctionData({
abi,
functionName: "liquidateWithPriceUpdate",
args: [vault.id, vaas],
});
const permission = this.createPermission(vault.id);
const targetCallValue = BigInt(priceUpdates.length);
let sellTokens;
if (targetCallValue > 0 && vault.tokenDebt == this.wethContract) {
sellTokens = [
{
token: this.wethContract,
amount: targetCallValue + vault.amountDebt,
},
];
} else {
sellTokens = [
{ token: vault.tokenDebt, amount: vault.amountDebt },
{ token: this.wethContract, amount: targetCallValue },
];
}
const opportunity: OpportunityParams = {
chainId: this.chainId,
targetContract: this.vaultContract,
targetCalldata: calldata,
permissionKey: permission,
targetCallValue: targetCallValue,
buyTokens: [
{ token: vault.tokenCollateral, amount: vault.amountCollateral },
],
sellTokens: sellTokens,
};
return opportunity;
}
private isLiquidatable(vault: VaultWithId): boolean {
if (
!this.prices[vault.tokenIdCollateral] ||
!this.prices[vault.tokenIdDebt]
) {
return false;
}
const priceCollateral = BigInt(
this.prices[vault.tokenIdCollateral].getPriceUnchecked().price
);
const priceDebt = BigInt(
this.prices[vault.tokenIdDebt].getPriceUnchecked().price
);
const valueCollateral = priceCollateral * vault.amountCollateral;
const valueDebt = priceDebt * vault.amountDebt;
if (valueDebt * vault.minHealthRatio > valueCollateral * 10n ** 18n) {
const health = Number(valueCollateral) / Number(valueDebt);
console.log(`Vault ${vault.id} is undercollateralized health: ${health}`);
return true;
}
return false;
}
private createPermission(vaultId: bigint) {
const permissionPayload = encodeAbiParameters(
[{ type: "uint256", name: "vaultId" }],
[vaultId]
);
const permission = encodeAbiParameters(
[
{ type: "address", name: "contract" },
{ type: "bytes", name: "vaultId" },
],
[this.vaultContract, permissionPayload]
);
return permission;
}
}
const argv = yargs(hideBin(process.argv))
.option("express-relay-endpoint", {
description:
"Express relay endpoint. e.g: https://per-staging.dourolabs.app/",
type: "string",
default: "https://per-staging.dourolabs.app/",
})
.option("pyth-endpoint", {
description: "Pyth endpoint to use for fetching prices",
type: "string",
default: "https://hermes.pyth.network",
})
.option("chain-id", {
description: "Chain id to send opportunities for. e.g: sepolia",
type: "string",
demandOption: true,
})
.option("weth-contract", {
description: "wrapped eth contract address",
type: "string",
demandOption: true,
})
.option("vault-contract", {
description: "Dummy token vault contract address",
type: "string",
demandOption: true,
})
.option("only-recent", {
description:
"Instead of checking all vaults, only check recent ones. Specify the number of recent vaults to check",
type: "number",
})
.help()
.alias("help", "h")
.parseSync();
async function run() {
const monitor = new ProtocolMonitor(
argv.expressRelayEndpoint,
argv.pythEndpoint,
argv.chainId,
checkAddress(argv.wethContract),
checkAddress(argv.vaultContract),
argv.onlyRecent
);
await monitor.start();
}
run();

View File

@ -0,0 +1,15 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"declaration": true,
"rootDir": "src/",
"outDir": "./tslib",
"strict": true,
"esModuleInterop": true,
"resolveJsonModule": true
},
"include": ["src"],
"exclude": ["node_modules", "**/__tests__/*"]
}

View File

@ -45,6 +45,7 @@
"@types/yargs": "^17.0.10",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"@typescript-eslint/parser": "^5.21.0",
"@pythnetwork/pyth-evm-js": "*",
"eslint": "^8.56.0",
"jest": "^27.5.1",
"prettier": "^2.6.2",

621
package-lock.json generated
View File

@ -8,6 +8,7 @@
"name": "root",
"version": "0.0.1",
"workspaces": [
"express_relay/examples/easy_lend",
"express_relay/sdk/js",
"express_relay/sdk/solidity",
"governance/xc_admin/packages/*",
@ -650,6 +651,371 @@
}
}
},
"express_relay/examples/easy_lend": {
"name": "easylend",
"version": "0.1.0",
"license": "Apache-2.0",
"dependencies": {
"@openzeppelin/contracts": "^4.5.0",
"@pythnetwork/express-relay-evm-js": "*",
"@pythnetwork/express-relay-sdk-solidity": "*",
"@pythnetwork/pyth-evm-js": "*",
"@pythnetwork/pyth-sdk-solidity": "*",
"ts-node": "^10.9.1",
"typescript": "^5.3.3",
"viem": "^2.7.6"
},
"devDependencies": {
"@types/yargs": "^17.0.10",
"eslint": "^8.56.0",
"prettier": "^2.6.2",
"typedoc": "^0.25.7",
"typescript": "^5.1",
"yargs": "^17.4.1"
}
},
"express_relay/examples/easy_lend/node_modules/@eslint/eslintrc": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
"integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
"dev": true,
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
"espree": "^9.6.0",
"globals": "^13.19.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
"minimatch": "^3.1.2",
"strip-json-comments": "^3.1.1"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"express_relay/examples/easy_lend/node_modules/@eslint/js": {
"version": "8.57.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
"integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"express_relay/examples/easy_lend/node_modules/@humanwhocodes/config-array": {
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
"dev": true,
"dependencies": {
"@humanwhocodes/object-schema": "^2.0.2",
"debug": "^4.3.1",
"minimatch": "^3.0.5"
},
"engines": {
"node": ">=10.10.0"
}
},
"express_relay/examples/easy_lend/node_modules/@humanwhocodes/object-schema": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
"integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
"dev": true
},
"express_relay/examples/easy_lend/node_modules/@noble/curves": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
"integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
"dependencies": {
"@noble/hashes": "1.3.2"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"express_relay/examples/easy_lend/node_modules/@noble/hashes": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"express_relay/examples/easy_lend/node_modules/@scure/bip32": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.2.tgz",
"integrity": "sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==",
"dependencies": {
"@noble/curves": "~1.2.0",
"@noble/hashes": "~1.3.2",
"@scure/base": "~1.1.2"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"express_relay/examples/easy_lend/node_modules/@scure/bip39": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz",
"integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==",
"dependencies": {
"@noble/hashes": "~1.3.0",
"@scure/base": "~1.1.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"express_relay/examples/easy_lend/node_modules/abitype": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.0.tgz",
"integrity": "sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ==",
"funding": {
"url": "https://github.com/sponsors/wevm"
},
"peerDependencies": {
"typescript": ">=5.0.4",
"zod": "^3 >=3.22.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
},
"zod": {
"optional": true
}
}
},
"express_relay/examples/easy_lend/node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"express_relay/examples/easy_lend/node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"express_relay/examples/easy_lend/node_modules/eslint": {
"version": "8.57.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.4",
"@eslint/js": "8.57.0",
"@humanwhocodes/config-array": "^0.11.14",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"@ungap/structured-clone": "^1.2.0",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
"debug": "^4.3.2",
"doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^7.2.2",
"eslint-visitor-keys": "^3.4.3",
"espree": "^9.6.1",
"esquery": "^1.4.2",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
"file-entry-cache": "^6.0.1",
"find-up": "^5.0.0",
"glob-parent": "^6.0.2",
"globals": "^13.19.0",
"graphemer": "^1.4.0",
"ignore": "^5.2.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"is-path-inside": "^3.0.3",
"js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
"optionator": "^0.9.3",
"strip-ansi": "^6.0.1",
"text-table": "^0.2.0"
},
"bin": {
"eslint": "bin/eslint.js"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"express_relay/examples/easy_lend/node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true,
"dependencies": {
"locate-path": "^6.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"express_relay/examples/easy_lend/node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"dependencies": {
"is-glob": "^4.0.3"
},
"engines": {
"node": ">=10.13.0"
}
},
"express_relay/examples/easy_lend/node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dev": true,
"dependencies": {
"p-locate": "^5.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"express_relay/examples/easy_lend/node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"dependencies": {
"yocto-queue": "^0.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"express_relay/examples/easy_lend/node_modules/p-locate": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"dev": true,
"dependencies": {
"p-limit": "^3.0.2"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"express_relay/examples/easy_lend/node_modules/typescript": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
"integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==",
"devOptional": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"express_relay/examples/easy_lend/node_modules/viem": {
"version": "2.8.13",
"resolved": "https://registry.npmjs.org/viem/-/viem-2.8.13.tgz",
"integrity": "sha512-jEbRUjsiBwmoDr3fnKL1Bh1GhK5ERhmZcPLeARtEaQoBTPB6bcO2siKhNPVOF8qrYRnGHGQrZHncBWMQhTjGYg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/wevm"
}
],
"dependencies": {
"@adraffy/ens-normalize": "1.10.0",
"@noble/curves": "1.2.0",
"@noble/hashes": "1.3.2",
"@scure/bip32": "1.3.2",
"@scure/bip39": "1.2.1",
"abitype": "1.0.0",
"isows": "1.0.3",
"ws": "8.13.0"
},
"peerDependencies": {
"typescript": ">=5.0.4"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"express_relay/examples/easy_lend/node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dev": true,
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"express_relay/examples/easy_lend/node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true,
"engines": {
"node": ">=12"
}
},
"express_relay/sdk/js": {
"name": "@pythnetwork/express-relay-evm-js",
"version": "0.2.0",
@ -663,6 +1029,7 @@
"ws": "^8.16.0"
},
"devDependencies": {
"@pythnetwork/pyth-evm-js": "*",
"@types/yargs": "^17.0.10",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"@typescript-eslint/parser": "^5.21.0",
@ -27840,6 +28207,10 @@
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
},
"node_modules/easylend": {
"resolved": "express_relay/examples/easy_lend",
"link": true
},
"node_modules/ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@ -67903,6 +68274,7 @@
"@pythnetwork/express-relay-evm-js": {
"version": "file:express_relay/sdk/js",
"requires": {
"@pythnetwork/pyth-evm-js": "*",
"@types/yargs": "^17.0.10",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"@typescript-eslint/parser": "^5.21.0",
@ -83307,6 +83679,255 @@
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
},
"easylend": {
"version": "file:express_relay/examples/easy_lend",
"requires": {
"@openzeppelin/contracts": "^4.5.0",
"@pythnetwork/express-relay-evm-js": "*",
"@pythnetwork/express-relay-sdk-solidity": "*",
"@pythnetwork/pyth-evm-js": "*",
"@pythnetwork/pyth-sdk-solidity": "*",
"@types/yargs": "^17.0.10",
"eslint": "^8.56.0",
"prettier": "^2.6.2",
"ts-node": "^10.9.1",
"typedoc": "^0.25.7",
"typescript": "^5.1",
"viem": "^2.7.6",
"yargs": "^17.4.1"
},
"dependencies": {
"@eslint/eslintrc": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
"integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
"dev": true,
"requires": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
"espree": "^9.6.0",
"globals": "^13.19.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
"minimatch": "^3.1.2",
"strip-json-comments": "^3.1.1"
}
},
"@eslint/js": {
"version": "8.57.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
"integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
"dev": true
},
"@humanwhocodes/config-array": {
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
"dev": true,
"requires": {
"@humanwhocodes/object-schema": "^2.0.2",
"debug": "^4.3.1",
"minimatch": "^3.0.5"
}
},
"@humanwhocodes/object-schema": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
"integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
"dev": true
},
"@noble/curves": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
"integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
"requires": {
"@noble/hashes": "1.3.2"
}
},
"@noble/hashes": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ=="
},
"@scure/bip32": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.2.tgz",
"integrity": "sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==",
"requires": {
"@noble/curves": "~1.2.0",
"@noble/hashes": "~1.3.2",
"@scure/base": "~1.1.2"
}
},
"@scure/bip39": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz",
"integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==",
"requires": {
"@noble/hashes": "~1.3.0",
"@scure/base": "~1.1.0"
}
},
"abitype": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.0.tgz",
"integrity": "sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ==",
"requires": {}
},
"cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
}
},
"escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true
},
"eslint": {
"version": "8.57.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
"dev": true,
"requires": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.4",
"@eslint/js": "8.57.0",
"@humanwhocodes/config-array": "^0.11.14",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"@ungap/structured-clone": "^1.2.0",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
"debug": "^4.3.2",
"doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^7.2.2",
"eslint-visitor-keys": "^3.4.3",
"espree": "^9.6.1",
"esquery": "^1.4.2",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
"file-entry-cache": "^6.0.1",
"find-up": "^5.0.0",
"glob-parent": "^6.0.2",
"globals": "^13.19.0",
"graphemer": "^1.4.0",
"ignore": "^5.2.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"is-path-inside": "^3.0.3",
"js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
"optionator": "^0.9.3",
"strip-ansi": "^6.0.1",
"text-table": "^0.2.0"
}
},
"find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true,
"requires": {
"locate-path": "^6.0.0",
"path-exists": "^4.0.0"
}
},
"glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"requires": {
"is-glob": "^4.0.3"
}
},
"locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dev": true,
"requires": {
"p-locate": "^5.0.0"
}
},
"p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"requires": {
"yocto-queue": "^0.1.0"
}
},
"p-locate": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"dev": true,
"requires": {
"p-limit": "^3.0.2"
}
},
"typescript": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
"integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==",
"devOptional": true
},
"viem": {
"version": "2.8.13",
"resolved": "https://registry.npmjs.org/viem/-/viem-2.8.13.tgz",
"integrity": "sha512-jEbRUjsiBwmoDr3fnKL1Bh1GhK5ERhmZcPLeARtEaQoBTPB6bcO2siKhNPVOF8qrYRnGHGQrZHncBWMQhTjGYg==",
"requires": {
"@adraffy/ens-normalize": "1.10.0",
"@noble/curves": "1.2.0",
"@noble/hashes": "1.3.2",
"@scure/bip32": "1.3.2",
"@scure/bip39": "1.2.1",
"abitype": "1.0.0",
"isows": "1.0.3",
"ws": "8.13.0"
}
},
"yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dev": true,
"requires": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
}
},
"yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true
}
}
},
"ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",

View File

@ -1,6 +1,7 @@
{
"name": "root",
"workspaces": [
"express_relay/examples/easy_lend",
"express_relay/sdk/js",
"express_relay/sdk/solidity",
"governance/xc_admin/packages/*",