Skip to content

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:

TypeType String
Kaizen:TransferKaizen:Transfer(uint64 timestamp,address from,address to,uint64 amount)
Kaizen:NominateApiWalletKaizen:NominateApiWallet(uint64 timestamp,address from,address wallet,uint64 expiry)
Kaizen:RevokeApiWalletKaizen:RevokeApiWallet(uint64 timestamp,address from,address wallet)
Kaizen:WithdrawKaizen:Withdraw(uint64 timestamp,address from,uint64 amount,address destination)
Kaizen:RfqSubmitKaizen:RfqSubmit(uint64 timestamp,address from,bytes32 quoteHash)
Kaizen:RfqSettleKaizen: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

  1. Compute type hash: keccak256(typeString)
  2. Encode data: ABI-encode all fields (addresses padded to 32 bytes)
  3. Compute struct hash: keccak256(typeHash || encodedData)
  4. Compute domain separator: keccak256(domainTypeHash || nameHash || versionHash || chainId)
  5. Compute message hash: keccak256("\x19\x01" || domainSeparator || structHash)
  6. 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

  1. Use account.sign() - Not signMessage() which adds a prefix
  2. Little-endian chainId - Matches Rust's alloy_primitives::U256 Borsh serialization
  3. 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);