Signer
The KaizenSigner handles transaction signing using EIP-712 typed structured data.
Creating a Signer
import { createSigner, KaizenSigner } from "@kaizen-core/sdk";
// Using factory function
const signer = createSigner(
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
1 // chainId
);
// Using class directly
const signer2 = new KaizenSigner({ privateKey: "0x..." }, 1);
console.log(`Signer address: ${signer.address}`);Signing Transactions
import { transfer, parseUSDC } from "@kaizen-core/sdk";
// Create payload
const payload = transfer("0x...", parseUSDC("100.00"));
// Sign with timestamp (for replay protection)
const timestamp = BigInt(Date.now());
const signedTx = await signer.signTransaction(payload, timestamp);
console.log({
from: signedTx.from,
timestamp: signedTx.timestamp,
payload: signedTx.payload, // Borsh-serialized payload (hex)
signature: signedTx.signature, // EIP-712 signature (hex)
hash: signedTx.hash, // Transaction hash
encoded: signedTx.encoded, // Full Borsh-encoded transaction (hex)
});Signing Arbitrary Messages
// Sign a string message
const signature = await signer.signMessage("Hello, Kaizen!");
// Sign raw bytes
const signature2 = await signer.signMessage(new Uint8Array([1, 2, 3, 4]));Signing Solver Quotes
When executing an RFQ thesis, the user must sign the solver quote to authorize it. This is separate from the EIP-712 transaction signature.
import type { SolverQuote } from "@kaizen-core/sdk";
// Sign a solver quote (user authorizing the thesis)
const userSignature = await signer.signQuote(quote);
// The signature is over the quote hash: keccak256(borsh_serialize(quote))Computing Quote Hash
You can also compute the quote hash separately for verification:
import { computeQuoteHash } from "@kaizen-core/sdk";
const quoteHash = computeQuoteHash(quote);
console.log(`Quote hash: ${quoteHash}`);Full RFQ Execution Flow
import { createSigner, computeQuoteHash } from "@kaizen-core/sdk";
const signer = createSigner("0x...");
// 1. Get quote from solver
const response = await fetch("http://solver:4000/quote", {
method: "POST",
body: JSON.stringify({
/* quote request */
}),
});
const { quote } = await response.json();
// 2. Reconstruct SolverQuote with proper types
const solverQuote = {
user: quote.user,
thesisType: quote.thesisType,
oraclePair: quote.oraclePair,
priceRange: {
lower: BigInt(quote.priceRange.lower),
upper: BigInt(quote.priceRange.upper),
},
betAmount: BigInt(quote.betAmount),
payout: BigInt(quote.payout),
startTime: quote.startTime,
endTime: quote.endTime,
deadline: quote.deadline,
signature: quote.signature,
};
// 3. Sign the quote hash
const userSignature = await signer.signQuote(solverQuote);
// 4. Execute via solver
await fetch("http://solver:4000/execute", {
method: "POST",
body: JSON.stringify({ quote, userSignature }),
});EIP-712 Implementation Details
The SDK implements EIP-712 signing that's compatible with the Rust backend.
Type Hashes
Each transaction type has a corresponding EIP-712 type hash. All types use the Kaizen: prefix convention:
| Type | Type String |
|---|---|
Kaizen:Transfer | Kaizen:Transfer(uint64 timestamp,address from,address to,uint64 amount) |
Kaizen:NominateApiWallet | Kaizen:NominateApiWallet(uint64 timestamp,address from,address wallet,uint64 expiry) |
Kaizen:RevokeApiWallet | Kaizen:RevokeApiWallet(uint64 timestamp,address from,address wallet) |
Kaizen:Withdraw | Kaizen:Withdraw(uint64 timestamp,address from,uint64 amount,address destination) |
Kaizen:RfqSubmit | Kaizen:RfqSubmit(uint64 timestamp,address from,bytes32 quoteHash) |
Kaizen:RfqSettle | Kaizen:RfqSettle(uint64 timestamp,address from,uint64 thesisId) |
Domain Separator
const domain = {
name: "Kaizen",
version: "1",
chainId: 1,
};
// Domain type: EIP712Domain(string name,string version,uint256 chainId)Signing Process
- Compute type hash:
keccak256(typeString) - Encode data: ABI-encode all fields (addresses padded to 32 bytes)
- Compute struct hash:
keccak256(typeHash || encodedData) - Compute domain separator:
keccak256(domainTypeHash || nameHash || versionHash || chainId) - Compute message hash:
keccak256("\x19\x01" || domainSeparator || structHash) - Sign the message hash with ECDSA
Important: v Parameter
The SDK converts the signature v parameter from 27/28 (viem default) to 0/1 (Rust expectation). This is handled by the internal normalizeSignatureV helper:
// Automatic conversion: 27/28 → 0/1
const signature = normalizeSignatureV(rawSignature);This conversion is applied automatically in signTransaction(), signQuote(), and buildSignedTransaction().
Cross-Language Compatibility
See EIP-712 Signatures for detailed information about cross-language compatibility between TypeScript and Rust.
Key Points
- Use
account.sign()- NotsignMessage()which adds a prefix - Little-endian
chainId- Matches Rust'salloy_primitives::U256Borsh serialization - v parameter conversion - Convert from 27/28 to 0/1
Advanced: Custom Payload Encoding
For debugging or custom implementations:
import { payloadToSchema, hexToBytes, bytesToHex } from "@kaizen-core/sdk";
import { serialize } from "@dao-xyz/borsh";
// Convert payload to Borsh schema
const schema = payloadToSchema(transfer("0x...", 100n));
// Serialize to bytes
const bytes = serialize(schema);
// Convert to hex
const hex = bytesToHex(bytes);