Core signing utilities for Eve Frontier wallets.
Early version. APIs may evolve as wallet and zkLogin based signing requirements mature.
- Node.js 25.6.1 or newer (see
.nvmrc) - bun 1.3.13 (project package manager)
This package publishes ESM-only entrypoints and TypeScript declarations from dist.
Install from npm (when published):
npm install @evefrontier/wallet-coreOr with bun:
bun add @evefrontier/wallet-coreSupported package subpath exports:
- @evefrontier/wallet-core/crypto
- @evefrontier/wallet-core/epoch
- @evefrontier/wallet-core/eve-token
- @evefrontier/wallet-core/jwt
- @evefrontier/wallet-core/sponsored-transaction
- @evefrontier/wallet-core/tenant
- @evefrontier/wallet-core/transaction
- @evefrontier/wallet-core/utils
- @evefrontier/wallet-core/wallet-features
Example imports:
import { ZKEd25519Keypair, type ZKProofData } from '@evefrontier/wallet-core/crypto'
import { SponsoredTransactionActions } from '@evefrontier/wallet-core/sponsored-transaction'Keypair signers available are:
- ZKEd25519Keypair
- ZKSecp256r1Keypair
- ZKWebCryptoSigner
These can be instantiated as follows:
// ZKEd25519Keypair
const keypair = new ZKEd25519Keypair()
// or
const keypair = ZKEd25519Keypair.generate()
// ZKSecp256r1Keypair
const keypair = new ZKSecp256r1Keypair()
// ZKWebCryptoSigner
const keypair = await ZKWebCryptoSigner.generate()After creating a signer the public key can be obtained to perform the zkLogin
const publicKey = keypair.getPublicKey()Then when the partial zkLogin signature, salt, max epoch, and JWT claim data
used for zkLogin address derivation are available, they can be applied to the
keypair classes by using applyZKProof.
keyPair.applyZKProof({
maxEpoch,
partialZkLoginSignature,
userSalt,
keyClaimName: 'sub',
keyClaimValue: tokenClaimSub,
aud: tokenClaimAud,
})applyZKProof switches the signer into zkLogin signing mode for future
signWithIntent, signTransaction, and signPersonalMessage calls. The
applied proof data is cloned into the signer, and getProofData() also returns
a copy, so mutating the original input or a returned proof-data object does not
mutate the signer's internal proof state. If the supplied partial signature
includes addressSeed, wallet-core does not store it; the signer computes the
address seed from userSalt, keyClaimName, keyClaimValue, and
aud. Most Enoki-backed flows use keyClaimName: 'sub' and pass
the JWT sub claim as keyClaimValue.
By default, applyZKProof validates wallet-core's expected JSON proof-data
shape before storing it:
maxEpochmust be a positive safe integer.partialZkLoginSignaturemust match the zkLogin proof input shape, excludingaddressSeed.- Proof points,
issBase64Details.value, andheaderBase64must be strings in the expected shape. Empty proof-service strings are not rejected by this shape check. issBase64Details.indexMod4must be an integer in the unsigned 8-bit serialization range. wallet-core does not assert an undocumented 0..3 semantic range.userSaltmust be a base-10 integer string from0to2^128 - 1, matching the salt bound in the Sui zkLogin integration guide.keyClaimName,keyClaimValue, andaudmust be non-empty JWT claim strings.keyClaimNameis the zkLogin key claim name used for address derivation, typicallysub; callers using a different stable JWT claim should pass that claim name and value.
Passing { skipValidation: true } skips only those explicit validation checks.
The signer still clones the supplied data, removes any supplied addressSeed,
and computes its own address seed, so malformed fields can still fail during
address-seed computation or later when they are used for signing.
If no partialZkLoginSignature is stored, signing falls back to the underlying
keypair or signer and returns a normal Sui signature.
Signing a transaction can be done as follows
const suiClient = getNewSuiGrpcClient() // returns a SuiGrpcClient
const tx = Transaction.from(transaction as string)
tx.setSenderIfNotSet(userAddress as string)
const txBytes = await tx.build({ client: suiClient })
const { signature, bytes } = await keypair.signTransaction(txBytes)Signing and executing can be done as follows:
const client = getNewSuiGrpcClient() // returns a SuiGrpcClient
const tx = Transaction.from(transaction as string)
tx.setSenderIfNotSet(userAddress as string)
const txb = await tx.build({ client })
const txResult = await client.core.signAndExecuteTransaction({
transaction: txb,
signer: keypair,
additionalSignatures: [],
include: {
effects: true,
},
})Signing a personal message can be done as follows:
const message = fromBase64('SGVsbG8gV29ybGQ=')
const { signature, bytes } =
await keypair.signPersonalMessage(message)Setup:
nvm use
bun install
bunx playwright install --with-deps chromiumRun checks:
bun run build
bun run lint
bun run test
bun run test:coveragebun run test runs the full Vitest suite, including browser projects, in headless mode. Use bun run test:node for the Node-only project and bun run test:browser for the browser projects.
Create a local package tarball:
bun install
bun run build
bun pm packInstall that tarball into another project:
bun add /absolute/path/to/evefrontier-wallet-core-0.0.1.tgzIf needed, validate consumption in the consumer project with TypeScript:
npx tsc --noEmit