Last active
September 16, 2025 18:08
-
-
Save denniswon/eca88d320b71023197aee23b1e42dab5 to your computer and use it in GitHub Desktop.
Signing RPC requests for Newton RPC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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