Skip to content

Commit

Permalink
refactor: improve typing on mana repo (#44)
Browse files Browse the repository at this point in the history
Our types have plenty of holes in it, due to disabled `noImplicitAny`
and not using `noUncheckedIndexedAccess`.

This PR creates base config which all repos should eventually use
and migrates mana to start using it.

Changes on mana side:
- use zod for validating request shape
- use Map instead of objects when it makes more sense
- remove implicit any in params
  • Loading branch information
Sekhmet authored Feb 22, 2024
1 parent fc964a7 commit b5bad3d
Show file tree
Hide file tree
Showing 16 changed files with 146 additions and 84 deletions.
2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"codegen": "checkpoint generate",
"lint": "eslint src/ test/ --ext .ts --fix",
"prebuild": "yarn codegen",
"build": "tsc -p tsconfig.build.json",
"build": "tsc",
"dev": "nodemon src/index.ts",
"start": "node dist/src/index.js",
"test": "vitest run"
Expand Down
4 changes: 0 additions & 4 deletions apps/api/tsconfig.build.json

This file was deleted.

6 changes: 4 additions & 2 deletions apps/mana/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@scure/bip32": "^1.3.3",
"@scure/bip39": "^1.2.2",
"@snapshot-labs/sx": "^0.1.0",
"@types/cors": "^2.8.17",
"abi-wan-kanabi": "^2.0.0",
"async-mutex": "^0.4.0",
"connection-string": "^4.4.0",
Expand All @@ -32,7 +33,8 @@
"express": "^4.17.1",
"knex": "^2.5.1",
"pg": "^8.11.3",
"starknet": "5.25.0"
"starknet": "5.25.0",
"zod": "^3.22.4"
},
"devDependencies": {
"@snapshot-labs/eslint-config": "0.1.0-beta.13",
Expand All @@ -44,6 +46,6 @@
"nodemon": "^3.0.1",
"prettier": "^3.1.0",
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
"typescript": "^5.3.3"
}
}
12 changes: 6 additions & 6 deletions apps/mana/src/eth/dependencies.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { StaticJsonRpcProvider } from '@ethersproject/providers';
import { Wallet } from '@ethersproject/wallet';

const DEFAULT_INDEX = 0;
const SPACES_INDICIES = {
'0x65e4329e8c0fba31883b98e2cf3e81d3cdcac780': 1, // SekhmetDAO
'0x4d95a8be4f1d24d50cc0d7b12f5576fa4bbd892b': 2 // Labs
};
export const DEFAULT_INDEX = 0;
export const SPACES_INDICIES = new Map([
['0x65e4329e8c0fba31883b98e2cf3e81d3cdcac780', 1], // SekhmetDAO
['0x4d95a8be4f1d24d50cc0d7b12f5576fa4bbd892b', 2] // Labs
]);

export function getEthereumWallet(mnemonic: string, index: number) {
const path = `m/44'/60'/0'/0/${index}`;
Expand All @@ -20,7 +20,7 @@ export const createWalletProxy = (mnemonic: string, chainId: number) => {
const normalizedSpaceAddress = spaceAddress.toLowerCase();

if (!signers.has(normalizedSpaceAddress)) {
const index = SPACES_INDICIES[normalizedSpaceAddress] || DEFAULT_INDEX;
const index = SPACES_INDICIES.get(normalizedSpaceAddress) || DEFAULT_INDEX;
const wallet = getEthereumWallet(mnemonic, index);
signers.set(normalizedSpaceAddress, wallet.connect(provider));
}
Expand Down
22 changes: 17 additions & 5 deletions apps/mana/src/eth/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
import express from 'express';
import z from 'zod';
import { createNetworkHandler, NETWORKS } from './rpc';
import { getEthereumWallet } from './dependencies';
import { DEFAULT_INDEX, SPACES_INDICIES } from '../stark/dependencies';
import { rpcError } from '../utils';
import { getEthereumWallet, DEFAULT_INDEX, SPACES_INDICIES } from './dependencies';

const router = express.Router();
const jsonRpcRequestSchema = z.object({
id: z.number(),
method: z.enum(['send', 'execute', 'executeQueuedProposal']),
params: z.any()
});

const handlers = Object.fromEntries(
Object.keys(NETWORKS).map(chainId => [chainId, createNetworkHandler(parseInt(chainId, 10))])
);

const router = express.Router();

router.post('/:chainId?', (req, res) => {
const chainId = req.params.chainId || '5';

const parsed = jsonRpcRequestSchema.safeParse(req.body);
if (!parsed.success) return rpcError(res, 400, parsed.error, 0);
const { id, method, params } = parsed.data;

const handler = handlers[chainId];
if (!handler) return rpcError(res, 404, new Error('Unsupported chainId'), id);

const { id, method, params } = req.body;
handler[method](id, params, res);
});

Expand All @@ -22,7 +34,7 @@ router.get('/relayers', (req, res) => {

const defaultRelayer = getEthereumWallet(mnemonic, DEFAULT_INDEX).address;
const relayers = Object.fromEntries(
Object.entries(SPACES_INDICIES).map(([spaceAddress, index]) => {
Array.from(SPACES_INDICIES).map(([spaceAddress, index]) => {
const { address } = getEthereumWallet(mnemonic, index);
return [spaceAddress, address];
})
Expand Down
30 changes: 16 additions & 14 deletions apps/mana/src/eth/rpc.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
import { Response } from 'express';
import {
clients,
evmPolygon,
evmArbitrum,
evmMainnet,
evmGoerli,
evmSepolia,
evmLineaGoerli
evmLineaGoerli,
EvmNetworkConfig
} from '@snapshot-labs/sx';
import { createWalletProxy } from './dependencies';
import { rpcError, rpcSuccess } from '../utils';

export const NETWORKS = {
137: evmPolygon,
42161: evmArbitrum,
1: evmMainnet,
5: evmGoerli,
11155111: evmSepolia,
59140: evmLineaGoerli
} as const;
export const NETWORKS = new Map<number, EvmNetworkConfig>([
[137, evmPolygon],
[42161, evmArbitrum],
[1, evmMainnet],
[5, evmGoerli],
[11155111, evmSepolia],
[59140, evmLineaGoerli]
]);

export const createNetworkHandler = (chainId: number) => {
const networkConfig = NETWORKS[chainId];
const networkConfig = NETWORKS.get(chainId);
if (!networkConfig) throw new Error('Unsupported chainId');

const getWallet = createWalletProxy(process.env.ETH_MNEMONIC || '', chainId);

const client = new clients.EvmEthereumTx({ networkConfig: networkConfig });
const client = new clients.EvmEthereumTx({ networkConfig });

async function send(id, params, res) {
async function send(id: number, params: any, res: Response) {
try {
const { signatureData, data } = params.envelope;
const { types } = signatureData;
Expand Down Expand Up @@ -67,7 +69,7 @@ export const createNetworkHandler = (chainId: number) => {
}
}

async function execute(id, params, res) {
async function execute(id: number, params: any, res: Response) {
try {
const { space, proposalId, executionParams } = params;
const signer = getWallet(space);
Expand All @@ -85,7 +87,7 @@ export const createNetworkHandler = (chainId: number) => {
}
}

async function executeQueuedProposal(id, params, res) {
async function executeQueuedProposal(id: number, params: any, res: Response) {
try {
const { space, executionStrategy, executionParams } = params;
const signer = getWallet(space);
Expand Down
4 changes: 2 additions & 2 deletions apps/mana/src/knex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export default knex({
database: connectionConfig.path[0],
user: connectionConfig.user,
password: connectionConfig.password,
host: connectionConfig.hosts[0].name,
port: connectionConfig.hosts[0].port,
host: connectionConfig?.hosts[0]?.name,
port: connectionConfig?.hosts[0]?.port,
ssl: Object.keys(sslConfig).length > 0 ? sslConfig : undefined
}
});
21 changes: 10 additions & 11 deletions apps/mana/src/stark/dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ import { NonceManager } from './nonce-manager';
const basePath = "m/44'/9004'/0'/0";
const contractAXclassHash = '0x1a736d6ed154502257f02b1ccdf4d9d1089f80811cd6acad48e6b6a9d1f2003';

const NODE_URLS = {
[constants.StarknetChainId.SN_MAIN]: process.env.STARKNET_MAINNET_RPC_URL,
[constants.StarknetChainId.SN_GOERLI]: process.env.STARKNET_GOERLI_RPC_URL,
[constants.StarknetChainId.SN_SEPOLIA]: process.env.STARKNET_SEPOLIA_RPC_URL
};

const NODE_URLS = new Map<string, string | undefined>([
[constants.StarknetChainId.SN_MAIN, process.env.STARKNET_MAINNET_RPC_URL],
[constants.StarknetChainId.SN_GOERLI, process.env.STARKNET_GOERLI_RPC_URL],
[constants.StarknetChainId.SN_SEPOLIA, process.env.STARKNET_SEPOLIA_RPC_URL]
]);
export function getProvider(chainId: string) {
return new RpcProvider({ nodeUrl: NODE_URLS[chainId] });
return new RpcProvider({ nodeUrl: NODE_URLS.get(chainId) });
}

export function getStarknetAccount(mnemonic: string, index: number) {
Expand All @@ -38,16 +37,16 @@ export function getStarknetAccount(mnemonic: string, index: number) {
}

export const DEFAULT_INDEX = 1;
export const SPACES_INDICIES = {
'0x040e337fb53973b08343ce983369c1d9e6249ba011e929347288e4d8b590d048': 2
};
export const SPACES_INDICIES = new Map([
['0x040e337fb53973b08343ce983369c1d9e6249ba011e929347288e4d8b590d048', 2]
]);

export function createAccountProxy(mnemonic: string, provider: RpcProvider) {
const accounts = new Map<number, { account: Account; nonceManager: NonceManager }>();

return (spaceAddress: string) => {
const normalizedSpaceAddress = validateAndParseAddress(spaceAddress);
const index = SPACES_INDICIES[normalizedSpaceAddress] || DEFAULT_INDEX;
const index = SPACES_INDICIES.get(normalizedSpaceAddress) || DEFAULT_INDEX;

if (!accounts.has(index)) {
const { address, privateKey } = getStarknetAccount(mnemonic, index);
Expand Down
54 changes: 35 additions & 19 deletions apps/mana/src/stark/herodotus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,40 @@ import { clients } from '@snapshot-labs/sx';
import * as db from '../db';
import { getClient } from './networks';

const HERODOTUS_API_KEY = process.env.HERODOTUS_API_KEY || '';
const HERODOTUS_MAPPING = {
[constants.StarknetChainId.SN_MAIN]: {
DESTINATION_CHAIN_ID: 'STARKNET',
ACCUMULATES_CHAIN_ID: '1',
FEE: '100000'
},
[constants.StarknetChainId.SN_GOERLI]: {
DESTINATION_CHAIN_ID: 'SN_GOERLI',
ACCUMULATES_CHAIN_ID: '5',
FEE: '0'
},
[constants.StarknetChainId.SN_SEPOLIA]: {
DESTINATION_CHAIN_ID: 'SN_SEPOLIA',
ACCUMULATES_CHAIN_ID: '11155111',
FEE: '0'
}
type HerodotusConfig = {
DESTINATION_CHAIN_ID: string;
ACCUMULATES_CHAIN_ID: string;
FEE: string;
};

const HERODOTUS_API_KEY = process.env.HERODOTUS_API_KEY || '';
const HERODOTUS_MAPPING = new Map<string, HerodotusConfig>([
[
constants.StarknetChainId.SN_MAIN,
{
DESTINATION_CHAIN_ID: 'STARKNET',
ACCUMULATES_CHAIN_ID: '1',
FEE: '100000'
}
],
[
constants.StarknetChainId.SN_GOERLI,
{
DESTINATION_CHAIN_ID: 'SN_GOERLI',
ACCUMULATES_CHAIN_ID: '5',
FEE: '0'
}
],
[
constants.StarknetChainId.SN_SEPOLIA,
{
DESTINATION_CHAIN_ID: 'SN_SEPOLIA',
ACCUMULATES_CHAIN_ID: '11155111',
FEE: '0'
}
]
]);

const controller = new clients.HerodotusController();

type ApiProposal = {
Expand Down Expand Up @@ -56,7 +71,7 @@ async function getStatus(id: string) {
}

async function submitBatch(proposal: ApiProposal) {
const mapping = HERODOTUS_MAPPING[proposal.chainId];
const mapping = HERODOTUS_MAPPING.get(proposal.chainId);
if (!mapping) throw new Error('Invalid chainId');

const { DESTINATION_CHAIN_ID, ACCUMULATES_CHAIN_ID, FEE } = mapping;
Expand Down Expand Up @@ -132,6 +147,7 @@ export async function registerProposal(proposal: ApiProposal) {
export async function processProposal(proposal: DbProposal) {
if (!proposal.herodotusId) {
const [, l1TokenAddress] = proposal.id.split('-');
if (!l1TokenAddress) throw new Error('Invalid proposal id');

return submitBatch({
...proposal,
Expand All @@ -154,7 +170,7 @@ export async function processProposal(proposal: DbProposal) {

const { getAccount } = getClient(proposal.chainId);
const { account, nonceManager } = getAccount('0x0');
const mapping = HERODOTUS_MAPPING[proposal.chainId];
const mapping = HERODOTUS_MAPPING.get(proposal.chainId);
if (!mapping) throw new Error('Invalid chainId');

const { DESTINATION_CHAIN_ID, ACCUMULATES_CHAIN_ID } = mapping;
Expand Down
19 changes: 16 additions & 3 deletions apps/mana/src/stark/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
import express from 'express';
import z from 'zod';
import { createNetworkHandler } from './rpc';
import { rpcError } from '../utils';
import { NETWORKS } from './networks';
import { DEFAULT_INDEX, SPACES_INDICIES, getStarknetAccount } from './dependencies';

const router = express.Router();
const jsonRpcRequestSchema = z.object({
id: z.number(),
method: z.enum(['send', 'registerTransaction', 'registerProposal']),
params: z.any()
});

const handlers = Object.fromEntries(
Object.keys(NETWORKS).map(chainId => [chainId, createNetworkHandler(chainId)])
);

const router = express.Router();

router.post('/:chainId', (req, res) => {
const chainId = req.params.chainId;

const parsed = jsonRpcRequestSchema.safeParse(req.body);
if (!parsed.success) return rpcError(res, 400, parsed.error, 0);
const { id, method, params } = parsed.data;

const handler = handlers[chainId];
if (!handler) return rpcError(res, 404, new Error('Unsupported chainId'), id);

const { id, method, params } = req.body;
handler[method](id, params, res);
});

Expand All @@ -22,7 +35,7 @@ router.get('/relayers', (req, res) => {

const defaultRelayer = getStarknetAccount(mnemonic, DEFAULT_INDEX).address;
const relayers = Object.fromEntries(
Object.entries(SPACES_INDICIES).map(([spaceAddress, index]) => {
Array.from(SPACES_INDICIES).map(([spaceAddress, index]) => {
const { address } = getStarknetAccount(mnemonic, index);
return [spaceAddress, address];
})
Expand Down
14 changes: 10 additions & 4 deletions apps/mana/src/stark/networks.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import { Account, RpcProvider, constants } from 'starknet';
import { clients, starknetMainnet, starknetGoerli, starknetSepolia } from '@snapshot-labs/sx';
import {
clients,
starknetMainnet,
starknetGoerli,
starknetSepolia,
NetworkConfig
} from '@snapshot-labs/sx';
import { getProvider, createAccountProxy } from './dependencies';
import { NonceManager } from './nonce-manager';

export const NETWORKS = {
export const NETWORKS: Record<string, NetworkConfig> = {
[constants.StarknetChainId.SN_MAIN]: starknetMainnet,
[constants.StarknetChainId.SN_GOERLI]: starknetGoerli,
[constants.StarknetChainId.SN_SEPOLIA]: starknetSepolia
} as const;
};

const clientsMap = new Map<
string,
{
provider: RpcProvider;
client: clients.StarknetTx;
getAccount: (spaceAddress) => { account: Account; nonceManager: NonceManager };
getAccount: (spaceAddress: string) => { account: Account; nonceManager: NonceManager };
}
>();

Expand Down
Loading

0 comments on commit b5bad3d

Please sign in to comment.