Skip to content

Commit

Permalink
SignRequest: Determine Broadcast Target (#110)
Browse files Browse the repository at this point in the history
  • Loading branch information
bh2smith authored Nov 14, 2024
1 parent c5646d7 commit a63948f
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 2 deletions.
51 changes: 50 additions & 1 deletion src/decode/sign-request.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { isRlpHex, isTransactionSerializable } from "near-ca";
import { EvmMessage, isRlpHex, isTransactionSerializable } from "near-ca";
import { parseTransaction, TransactionSerializable } from "viem";

import {
DecodedTxData,
parseEip712TypedData,
parseUserOperation,
SafeEncodedSignRequest,
UserOperation,
} from "../types";
import {
decodeRlpHex,
Expand Down Expand Up @@ -56,3 +58,50 @@ export function decodeTxData({
`decodeTxData: Invalid or unsupported message format ${data}`
);
}

/**
* Represents different types of broadcastable messages
*/
export type BroadcastTarget =
| { type: "evm"; transaction: TransactionSerializable }
| { type: "bundler"; userOp: UserOperation };

/**
* Determines where and how an EVM message should be broadcast
* @param evmMessage - The message to be analyzed
* @returns Information about how to broadcast the message, or null if invalid
*/
export function determineBroadcastTarget(
evmMessage: EvmMessage
): BroadcastTarget | null {
// Case 1: User Operation
if (typeof evmMessage === "string") {
try {
const parsed = parseUserOperation(evmMessage);
if (parsed) {
return {
type: "bundler",
userOp: parsed,
};
}
} catch (error) {
console.warn("Failed to parse potential UserOperation:", error);
}
}
// Case 2: RLP Encoded EVM transaction
if (isRlpHex(evmMessage)) {
return {
type: "evm",
transaction: parseTransaction(evmMessage),
};
}
// Case 3: Serializable Transaction
if (isTransactionSerializable(evmMessage)) {
return {
type: "evm",
transaction: evmMessage,
};
}

return null;
}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from "./constants";
export * from "./decode";
export * from "./lib/safe-message";

// TODO: Improve re-exports...
export {
Network,
BaseTx,
Expand All @@ -15,4 +16,5 @@ export {
signatureFromTxHash,
requestRouter as mpcRequestRouter,
EthTransactionParams,
isRlpHex,
} from "near-ca";
76 changes: 75 additions & 1 deletion tests/unit/decode/sign-request.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { EIP712TypedData } from "near-ca";
import { TransactionSerializableEIP1559 } from "viem";
import {
serializeTransaction,
TransactionSerializable,
TransactionSerializableEIP1559,
} from "viem";

import {
SafeEncodedSignRequest,
UserOperation,
decodeTxData,
determineBroadcastTarget,
} from "../../../src";
import {
decodeRlpHex,
Expand Down Expand Up @@ -212,3 +217,72 @@ describe("decoding functions", () => {
});
});
});

const validEIP1559Transaction: TransactionSerializable = {
to: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
value: BigInt(1000000000000000000), // 1 ETH
chainId: 1,
maxFeePerGas: 1n,
};
describe("determineBroadcastTarget", () => {
it("identifies EVM transactions", () => {
const rlpHex = serializeTransaction(validEIP1559Transaction);
const result = determineBroadcastTarget(rlpHex);

expect(result).toEqual({
type: "evm",
transaction: expect.any(Object),
});
});

it("identifies EVM transactions", () => {
const result = determineBroadcastTarget(validEIP1559Transaction);

expect(result).toEqual({
type: "evm",
transaction: expect.any(Object),
});
});

it("identifies UserOperations", () => {
const userOp = {
nonce: "0x0",
sender: "0x1f87B80f909a74d8Be617f350EC20a16CfC177D7",
factory: "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67",
callData:
"0x7bb374280000000000000000000000008d99f8b2710e6a3b94d9bf465a98e5273069acbd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002b00b00000000000000000000000000000000000000000000000000000000000062697474652f6e6561722d7361666500",
paymaster: "0x0000000000000039cd5e8aE05257CE51C473ddd1",
signature: "0x000000000000000000000000",
factoryData:
"0x1688f0b900000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c76200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000062697474652f77616c6c65740000000000000000000000000000000000000000000000000000000000000000000001e4b63e800d000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000002dd68b007b46fbe91b9a7c3eda5a7a1063cb5b47000000000000000000000000000000000000000000000000000000000000014000000000000000000000000075cf11467937ce3f2f357ce24ffc3dbf8fd5c2260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000523434f148b321a153e1c80f56e44c406c26636300000000000000000000000000000000000000000000000000000000000000648d0dc49f0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000075cf11467937ce3f2f357ce24ffc3dbf8fd5c2260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
callGasLimit: "0x1f2e9",
maxFeePerGas: "0x6e64fe30",
paymasterData: "0x000000673375fe000000000000",
preVerificationGas: "0xe6f7",
maxPriorityFeePerGas: "0x139638",
verificationGasLimit: "0x7bb14",
paymasterPostOpGasLimit: "0x1",
paymasterVerificationGasLimit: "0x6cbb",
};

const result = determineBroadcastTarget(JSON.stringify(userOp));

expect(result).toEqual({
type: "bundler",
userOp,
});
});

it("returns null for invalid messages", () => {
const invalidCases = [
"not valid json",
"{}",
JSON.stringify({ invalid: "userOp" }),
"0xinvalidhex",
];

invalidCases.forEach((testCase) => {
expect(determineBroadcastTarget(testCase)).toBeNull();
});
});
});

0 comments on commit a63948f

Please sign in to comment.