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"