Skip to content

Instantly share code, notes, and snippets.

@denniswon
Last active September 16, 2025 18:08
Show Gist options
  • Save denniswon/eca88d320b71023197aee23b1e42dab5 to your computer and use it in GitHub Desktop.
Save denniswon/eca88d320b71023197aee23b1e42dab5 to your computer and use it in GitHub Desktop.
Signing RPC requests for Newton RPC
import {
type Address,
type Hex,
encodePacked,
keccak256,
parseEther,
parseUnits,
type PrivateKeyAccount,
createWalletClient,
http,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";
// Type definitions matching the Rust structs
interface TaskIntent {
from: Address;
to: Address;
value: string; // Wei amount as string
data: Hex;
chain_id: string;
function_signature: Hex;
}
interface CreateTaskAndWaitRequest {
signature?: Hex;
policy_client: Address;
intent: TaskIntent;
quorum_number?: number[];
quorum_threshold_percentage?: number;
timeout?: number;
}
// Internal representation for signing (matches Rust ICreateTaskAndWaitRequest)
interface ITaskIntent {
from: Address;
to: Address;
value: bigint;
data: Hex;
chainId: bigint;
functionSignature: Hex;
}
interface ICreateTaskAndWaitRequest {
policyClient: Address;
intent: ITaskIntent;
quorumNumber: Hex;
quorumThresholdPercentage: number;
timeout: bigint;
}
// Convert external request to internal format
function convertToInternalRequest(request: CreateTaskAndWaitRequest): ICreateTaskAndWaitRequest {
// Convert quorum_number array to bytes
const quorumBytes = request.quorum_number?.length
? `0x${Buffer.from(new Uint8Array(request.quorum_number)).toString('hex')}` as Hex
: "0x" as Hex;
return {
policyClient: request.policy_client,
intent: {
from: request.intent.from,
to: request.intent.to,
value: BigInt(request.intent.value),
data: request.intent.data,
chainId: BigInt(request.intent.chain_id),
functionSignature: request.intent.function_signature,
},
quorumNumber: quorumBytes,
quorumThresholdPercentage: request.quorum_threshold_percentage || 0,
timeout: BigInt(request.timeout || 0),
};
}
// Generate hash for signing (matches Rust keccak256(abi_encode_packed))
function generateRequestHash(internalRequest: ICreateTaskAndWaitRequest): Hex {
// ABI encode packed equivalent using viem's encodePacked
return keccak256(
encodePacked(
[
"address", // policyClient
"address", // intent.from
"address", // intent.to
"uint256", // intent.value
"bytes", // intent.data
"uint256", // intent.chainId
"bytes", // intent.functionSignature
"bytes", // quorumNumber
"uint32", // quorumThresholdPercentage
"uint64", // timeout
],
[
internalRequest.policyClient,
internalRequest.intent.from,
internalRequest.intent.to,
internalRequest.intent.value,
internalRequest.intent.data,
internalRequest.intent.chainId,
internalRequest.intent.functionSignature,
internalRequest.quorumNumber,
internalRequest.quorumThresholdPercentage,
internalRequest.timeout,
]
)
);
}
// Sign the request using Viem
export async function signCreateTaskAndWaitRequest(
request: CreateTaskAndWaitRequest,
privateKey: Hex
): Promise<CreateTaskAndWaitRequest> {
// Create account from private key
const account = privateKeyToAccount(privateKey);
// Convert to internal format
const internalRequest = convertToInternalRequest(request);
// Generate hash
const hash = generateRequestHash(internalRequest);
// Sign the hash
const signature = await account.signMessage({
message: { raw: hash },
});
// Return request with signature
return {
...request,
signature,
};
}
// Alternative method using wallet client
export async function signCreateTaskAndWaitRequestWithWallet(
request: CreateTaskAndWaitRequest,
privateKey: Hex
): Promise<CreateTaskAndWaitRequest> {
const account = privateKeyToAccount(privateKey);
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http(),
});
const internalRequest = convertToInternalRequest(request);
const hash = generateRequestHash(internalRequest);
const signature = await walletClient.signMessage({
message: { raw: hash },
});
return {
...request,
signature,
};
}
// Example usage
export async function example() {
const request: CreateTaskAndWaitRequest = {
policy_client: "0x1234567890123456789012345678901234567890",
intent: {
from: "0x1234567890123456789012345678901234567890",
to: "0x0987654321098765432109876543210987654321",
value: parseEther("1").toString(), // 1 ETH in wei
data: "0x70a08231000000000000000000000000",
chain_id: "1",
function_signature: "0x70a08231",
},
quorum_number: [1],
quorum_threshold_percentage: 40,
timeout: 60,
};
const privateKey: Hex = "0x..."; // Your private key
try {
const signedRequest = await signCreateTaskAndWaitRequest(request, privateKey);
console.log("Signed request:", signedRequest);
// Now you can send this to the Newton RPC server
const rpcRequest = {
jsonrpc: "2.0",
method: "newton_createTaskAndWait",
params: signedRequest,
id: 1,
};
return rpcRequest;
} catch (error) {
console.error("Error signing request:", error);
throw error;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment