diff --git a/client/src/lib/components/NetworkInput.svelte b/client/src/lib/components/NetworkInput.svelte index 6bd0184b..8197ea18 100644 --- a/client/src/lib/components/NetworkInput.svelte +++ b/client/src/lib/components/NetworkInput.svelte @@ -1,4 +1,6 @@ diff --git a/client/src/lib/utils/stores.ts b/client/src/lib/utils/stores.ts index 830e5db9..bb326d72 100644 --- a/client/src/lib/utils/stores.ts +++ b/client/src/lib/utils/stores.ts @@ -1,12 +1,10 @@ -import { derived, writable } from "svelte/store"; +import { writable } from "svelte/store"; import { type NetworkData, Paseo } from "./networkData"; // Defaults to Paseo, being updated in Faucet.svelte export const testnet = writable(Paseo); -export const testnetName = derived(testnet, ($net) => $net.networkName); - interface FaucetOperation { success: boolean; hash: string; diff --git a/package.json b/package.json index e1fea887..47c330be 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ }, "dependencies": { "@eng-automation/js": "^2.1.0", + "@noble/hashes": "^1.5.0", "@polkadot-api/descriptors": "portal:.papi/descriptors", "@polkadot-labs/hdkd": "^0.0.6", "@polkadot-labs/hdkd-helpers": "^0.0.6", diff --git a/src/bot/index.ts b/src/bot/index.ts index 1168e8f8..7d73a20c 100644 --- a/src/bot/index.ts +++ b/src/bot/index.ts @@ -5,7 +5,7 @@ import { convertAmountToBn, convertBnAmountToNumber, formatAmount } from "#src/d import { isDripSuccessResponse } from "#src/guards"; import { logger } from "#src/logger"; import { getNetworkData } from "#src/papi/index"; -import { isAccountPrivileged } from "#src/utils"; +import { ethAddressToSS58, isAccountPrivileged } from "#src/utils"; import * as mSDK from "matrix-js-sdk"; import { AccountId } from "polkadot-api"; @@ -129,10 +129,15 @@ bot.on(mSDK.RoomEvent.Timeline, (event: mSDK.MatrixEvent) => { } const arg0_processed = arg0.trim().split(":"); - const address = arg0_processed[0]; + let address = arg0_processed[0]; const parachain_id = arg0_processed[1] ? arg0_processed[1] : ""; logger.debug(`Processed receiver to address ${address} and parachain id ${parachain_id}`); + if (address.startsWith("0x")) { + address = ethAddressToSS58(address, networkData.data.ethToSS58FillPrefix); + logger.debug(`Converted ETH address to ${address}`); + } + try { AccountId().enc(address); } catch (e) { diff --git a/src/faucet.e2e.ts b/src/faucet.e2e.ts index 966f4bb1..5d85f612 100644 --- a/src/faucet.e2e.ts +++ b/src/faucet.e2e.ts @@ -225,4 +225,6 @@ describe("Faucet E2E", () => { "Requester has reached their daily quota. Only request once per day", ); }); + + test.todo("Faucet drips to ethereum address"); }); diff --git a/src/papi/chains/e2e.ts b/src/papi/chains/e2e.ts index 8c2f413b..55ef4f75 100644 --- a/src/papi/chains/e2e.ts +++ b/src/papi/chains/e2e.ts @@ -24,6 +24,7 @@ export const networkData: NetworkData = { networkName: "Rococo", rpcEndpoint: "ws://host.docker.internal:9933/", matrixWhitelistPatterns: parityWhitelist, + ethToSS58FillPrefix: 0xee, }; export const networkApi: NetworkApi = { diff --git a/src/papi/chains/index.ts b/src/papi/chains/index.ts index 098038b9..a2947314 100644 --- a/src/papi/chains/index.ts +++ b/src/papi/chains/index.ts @@ -52,6 +52,8 @@ export interface NetworkData { dripAmount: string; balanceCap: number; matrixWhitelistPatterns: RegExp[]; + // Should be 0xEE for all our currenly supported chains, but could be different for other chains + ethToSS58FillPrefix: number; } export function getNetworkData(networkName: string): { data: NetworkData; api: NetworkApi } { diff --git a/src/papi/chains/paseo.ts b/src/papi/chains/paseo.ts index e9dc7f48..9caff57a 100644 --- a/src/papi/chains/paseo.ts +++ b/src/papi/chains/paseo.ts @@ -33,6 +33,7 @@ const networkData: NetworkData = { /^@purpletentacle:matrix\.org$/, /^@carlosala:matrix\.org$/, ], + ethToSS58FillPrefix: 0xee, }; export const networkApi: NetworkApi = { diff --git a/src/papi/chains/versi.ts b/src/papi/chains/versi.ts index 27131aeb..7697010f 100644 --- a/src/papi/chains/versi.ts +++ b/src/papi/chains/versi.ts @@ -24,6 +24,7 @@ const networkData: NetworkData = { networkName: "Versi", rpcEndpoint: "wss://versi-rpc-node-0.parity-versi.parity.io/", matrixWhitelistPatterns: parityWhitelist, + ethToSS58FillPrefix: 0xee, }; const networkApi: NetworkApi = { diff --git a/src/papi/chains/westend.ts b/src/papi/chains/westend.ts index e456ad05..c6236779 100644 --- a/src/papi/chains/westend.ts +++ b/src/papi/chains/westend.ts @@ -28,6 +28,7 @@ const networkData: NetworkData = { networkName: "Westend", rpcEndpoint: "wss://westend-rpc.polkadot.io/", matrixWhitelistPatterns: parityWhitelist, + ethToSS58FillPrefix: 0xee, }; export const networkApi: NetworkApi = { diff --git a/src/server/routes/actions.spec.ts b/src/server/routes/actions.spec.ts index f391ab1d..e4ff21e9 100644 --- a/src/server/routes/actions.spec.ts +++ b/src/server/routes/actions.spec.ts @@ -15,7 +15,7 @@ import router from "./actions"; jest.mock("../../dripper/dripperStorage"); jest.mock("../../dripper/polkadot/PolkadotActions", () => {}); jest.mock("../../logger", () => { - return { logger: { error: mockLoggerError } }; + return { logger: { error: mockLoggerError, debug: jest.fn() } }; }); jest.mock("../../dripper/DripRequestHandler", () => { diff --git a/src/server/routes/actions.ts b/src/server/routes/actions.ts index 64f4aa48..5c70ece2 100644 --- a/src/server/routes/actions.ts +++ b/src/server/routes/actions.ts @@ -4,6 +4,7 @@ import polkadotActions from "#src/dripper/polkadot/PolkadotActions"; import { convertAmountToBn, formatAmount } from "#src/dripper/polkadot/utils"; import { logger } from "#src/logger"; import { getNetworkData } from "#src/papi/index"; +import { ethAddressToSS58 } from "#src/utils"; import cors from "cors"; import express, { NextFunction, Request, Response } from "express"; @@ -54,10 +55,19 @@ const addressMiddleware = ( type PartialDrip = Partial & Pick; router.post>("/drip/web", addressMiddleware, async (req, res) => { - const { address, parachain_id, recaptcha } = req.body; + const { parachain_id, recaptcha } = req.body; if (!recaptcha) { return missingParameterError(res, "recaptcha"); } + + let { address } = req.body; + + logger.debug(`Dripping to ${address}, parachain id ${parachain_id}`); + if (address.startsWith("0x")) { + address = ethAddressToSS58(address, networkData.data.ethToSS58FillPrefix); + logger.debug(`Converted ETH address to ${address}`); + } + try { const dripResult = await dripRequestHandler.handleRequest({ external: true, diff --git a/src/utils.ts b/src/utils.ts index c2f07fd8..b2a67b88 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,7 @@ +import { concatBytes, hexToBytes } from "@noble/hashes/utils"; import { config } from "#src/config"; import { getNetworkData } from "#src/papi/index"; +import { AccountId } from "polkadot-api"; export function isAccountPrivileged(sender: string): boolean { const networkName = config.Get("NETWORK"); @@ -7,3 +9,13 @@ export function isAccountPrivileged(sender: string): boolean { return networkData.data.matrixWhitelistPatterns.some((pattern) => pattern.test(sender)); } + +// copypaste from https://github.com/paritytech/revive-remix/blob/2eca8b351f96734ffc94adc63b94f3587bd8d67d/libs/remix-ui/helper/src/lib/remix-ui-helper.ts#L93 +export function ethAddressToSS58(address: string, prefix: number): string { + // Remove '0x' prefix + const ethAddress = address.slice(2); + const ethAddressBytes = hexToBytes(ethAddress); + // Pad the address to 32 bytes with `0xEE` + const paddedAddress = concatBytes(ethAddressBytes, new Uint8Array(32 - ethAddressBytes.length).fill(prefix)); + return AccountId().dec(paddedAddress); +} diff --git a/yarn.lock b/yarn.lock index b0a8b68c..6d085a86 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5090,6 +5090,7 @@ __metadata: "@eng-automation/js": "npm:^2.1.0" "@eng-automation/js-style": "npm:^3.1.0" "@eng-automation/testing": "npm:^1.5.1" + "@noble/hashes": "npm:^1.5.0" "@polkadot-api/descriptors": "portal:.papi/descriptors" "@polkadot-labs/hdkd": "npm:^0.0.6" "@polkadot-labs/hdkd-helpers": "npm:^0.0.6"