Skip to content

Commit

Permalink
Wallet Connect Session Request Handler (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
bh2smith authored May 13, 2024
1 parent 236afa0 commit 09b7e00
Show file tree
Hide file tree
Showing 5 changed files with 1,483 additions and 27 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
},
"dependencies": {
"@near-wallet-selector/core": "^8.9.5",
"@walletconnect/web3wallet": "^1.12.0",
"elliptic": "^6.5.5",
"near-api-js": "^3.0.3",
"viem": "^2.10.3"
Expand Down
24 changes: 24 additions & 0 deletions src/chains/ethereum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
TypedData,
TypedDataDefinition,
parseTransaction,
TransactionSerializable,
} from "viem";
import {
BaseTx,
Expand All @@ -26,6 +27,8 @@ import { MultichainContract } from "../mpcContract";
import { buildTxPayload, addSignature, populateTx } from "../utils/transaction";
import { Network } from "../network";
import { pickValidSignature } from "../utils/signature";
import { Web3WalletTypes } from "@walletconnect/web3wallet";
import { wcRouter } from "../wallet-connect/handlers";

export class NearEthAdapter {
readonly mpcContract: MultichainContract;
Expand Down Expand Up @@ -247,4 +250,25 @@ export class NearEthAdapter {
serializeSignature({ r, s, yParity: 1 }),
];
}

/// Mintbase Wallet
async handleSessionRequest(request: Web3WalletTypes.SessionRequest): Promise<{
evmMessage: string | TransactionSerializable;
nearPayload: NearContractFunctionPayload;
}> {
const {
chainId,
request: { method, params },
} = request.params;
console.log(`Session Request of type ${method} for chainId ${chainId}`);
const { evmMessage, payload } = await wcRouter(method, chainId, params);
return {
nearPayload: this.mpcContract.encodeSignatureRequestTx({
path: this.derivationPath,
payload,
key_version: 0,
}),
evmMessage,
};
}
}
87 changes: 87 additions & 0 deletions src/wallet-connect/handlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {
Hex,
TransactionSerializable,
fromHex,
hashMessage,
hashTypedData,
keccak256,
serializeTransaction,
} from "viem";
import { populateTx, toPayload } from "../utils/transaction";

// Interface for Ethereum transaction parameters
export interface EthTransactionParams {
from: Hex;
to: Hex;
gas?: Hex;
value?: Hex;
data?: Hex;
}

// Interface for personal sign parameters (message and address)
export type PersonalSignParams = [Hex, Hex];

// Interface for complex structured parameters like EIP-712
export type TypedDataParams = [Hex, string];

type SessionRequestParams =
| EthTransactionParams[]
| PersonalSignParams
| TypedDataParams;

export async function wcRouter(
method: string,
chainId: string,
params: SessionRequestParams
): Promise<{
evmMessage: TransactionSerializable | string;
payload: number[];
}> {
switch (method) {
// I believe {personal,eth}_sign both get routed to the same place.
case "eth_sign":
case "personal_sign": {
const [messageHash, sender] = params as PersonalSignParams;
const message = fromHex(messageHash, "string");
console.log(`Message to be signed by ${sender}: ${message}`);
return {
evmMessage: fromHex(messageHash, "string"),
payload: toPayload(hashMessage(messageHash)),
};
}
case "eth_sendTransaction": {
const tx = params[0] as EthTransactionParams;
const transaction = await populateTx(
{
to: tx.to,
chainId: parseInt(stripEip155Prefix(chainId)),
value: fromHex(tx.value || "0x", "bigint"),
data: tx.data,
gas: tx.gas ? fromHex(tx.gas, "bigint") : undefined,
},
tx.from
);
return {
payload: toPayload(keccak256(serializeTransaction(transaction))),
evmMessage: transaction,
};
}
case "eth_signTypedData":
case "eth_signTypedData_v4": {
const [sender, dataString] = params as TypedDataParams;
const typedData = JSON.parse(dataString);
console.log(
`Received Typed Data signature request from ${sender}: ${JSON.stringify(typedData)}`
);
return {
evmMessage: dataString,
payload: toPayload(hashTypedData(typedData)),
};
}
}
throw new Error(`Unhandled session_request method: ${method}`);
}

function stripEip155Prefix(eip155Address: string): string {
return eip155Address.split(":").pop() ?? "";
}
115 changes: 115 additions & 0 deletions tests/wc.handlers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { TransactionSerializable, toHex } from "viem";
import {
EthTransactionParams,
PersonalSignParams,
wcRouter,
} from "../src/wallet-connect/handlers";

describe("Wallet Connect", () => {
const chainId = "eip155:11155111";

it("wcRouter: personal_sign", async () => {
const messageString = "Hello!";
const request = {
method: "personal_sign",
params: [
toHex(messageString),
"0xa61d98854f7ab25402e3d12548a2e93a080c1f97",
],
};

const { evmMessage, payload } = await wcRouter(
request.method,
chainId,
request.params as PersonalSignParams
);
expect(evmMessage).toEqual(messageString);
expect(payload).toEqual([
129, 83, 250, 146, 102, 140, 185, 9, 243, 111, 112, 21, 11, 157, 12, 23,
202, 85, 99, 164, 77, 162, 209, 137, 199, 133, 194, 59, 178, 150, 153, 78,
]);
});
it("wcRouter: sendTransaction", async () => {
const request = {
method: "eth_sendTransaction",
params: [
{
gas: "0xd31d",
value: "0x16345785d8a0000",
from: "0xa61d98854f7ab25402e3d12548a2e93a080c1f97",
to: "0xfff9976782d46cc05630d1f6ebab18b2324d6b14",
data: "0xd0e30db0",
},
],
};

const { evmMessage } = await wcRouter(
request.method,
chainId,
request.params as EthTransactionParams[]
);
const tx = evmMessage as TransactionSerializable;

delete tx.maxFeePerGas;
delete tx.maxPriorityFeePerGas;
delete tx.nonce;

expect(tx).toEqual({
account: "0xa61d98854f7ab25402e3d12548a2e93a080c1f97",
chainId: 11155111,
data: "0xd0e30db0",
gas: 54045n,
to: "0xfff9976782d46cc05630d1f6ebab18b2324d6b14",
value: 100000000000000000n,
});
/// can't test payload: its non-deterministic because of gas values!
});
it("wcRouter: eth_signTypedData", async () => {
const jsonStr = `{
"types": {
"Permit": [
{"name": "owner", "type": "address"},
{"name": "spender", "type": "address"},
{"name": "value", "type": "uint256"},
{"name": "nonce", "type": "uint256"},
{"name": "deadline", "type": "uint256"}
],
"EIP712Domain": [
{"name": "name", "type": "string"},
{"name": "version", "type": "string"},
{"name": "chainId", "type": "uint256"},
{"name": "verifyingContract", "type": "address"}
]
},
"domain": {
"name": "CoW Protocol Token",
"version": "1",
"chainId": "11155111",
"verifyingContract": "0x0625afb445c3b6b7b929342a04a22599fd5dbb59"
},
"primaryType": "Permit",
"message": {
"owner": "0xa61d98854f7ab25402e3d12548a2e93a080c1f97",
"spender": "0xc92e8bdf79f0507f65a392b0ab4667716bfe0110",
"value": "115792089237316195423570985008687907853269984665640564039457584007913129639935",
"nonce": "0",
"deadline": "1872873982"
}
}`;
const request = {
method: "eth_signTypedData_v4",
params: ["0xa61d98854f7ab25402e3d12548a2e93a080c1f97", jsonStr],
};

const { evmMessage, payload } = await wcRouter(
request.method,
chainId,
request.params as PersonalSignParams
);
expect(evmMessage).toEqual(request.params[1]);
expect(payload).toEqual([
154, 201, 197, 176, 122, 212, 161, 42, 56, 12, 218, 93, 39, 197, 249, 144,
53, 126, 250, 19, 85, 168, 82, 131, 104, 184, 46, 112, 237, 228, 48, 12,
]);
});
});
Loading

0 comments on commit 09b7e00

Please sign in to comment.