Developers · EVM wallet-control binding
Prove control of one EVM address for one action
This proves control of a self-custodial EVM address for one named action. It is not login, KYC, identity verification, wallet custody, a portfolio read, a balance check, token-gating, transaction approval, or a generic wallet-connect product. Sign this message to prove control for this one action. No transaction will be created or signed. Abraxas does not read your balances or hold your keys.
When to use it
Add this step only when an EVM partner action sets wallet binding to optional or required. Absence does not deny a valid optional action. Required fails closed without a current matching binding. Do not reuse Solana Wallet Standard assumptions.
This is a message proof only, no transaction.
Signing standard: eip191_personal_sign · Library: viem.verifyMessage / viem.recoverMessageAddress
Architecture
partner server -> issue EVM action contract (optional or required wallet_binding)
partner server -> issue domain-bound EIP-191 personal_sign challenge
holder -> signs the message only (no transaction, no balances)
partner server -> verify signature, store opaque binding_ref
partner server -> EVM adapter preflight with binding_ref
browser <- { ok, status, binding_ref, expires_at }Durable store migration
This public feature requires 094_evm_wallet_control_bindings.sql on both DEMO and Production before challenge or bind can succeed. Missing schema fails closed with store_unavailable. There is no in-process fallback.
Apply DEMO first, then Production, as separate operator steps. Do not auto-apply from Vercel.HMAC-SHA256 hashes of origin, policy, action, network, nonce, message, and normalized address plus opaque binding_ref, expiry, revocation, and reason_class. Never signatures, raw addresses, message text, RPC data, balances, or transactions.
Partner implementation
// Backend first. Issue the action contract, then optionally require an EVM message proof.
import { AbraxasEvmPartnerAdapter } from "@/lib/partner/evm";
import { issueEvmWalletChallenge, bindEvmWalletControl } from "@/lib/partner/evmWalletBinding";
const adapter = new AbraxasEvmPartnerAdapter({
partnerId: process.env.ABRAXAS_PARTNER_ID!,
policyId: process.env.ABRAXAS_POLICY_ID!,
policyVersion: 1,
requirePolicyVersion: true,
environment: "sandbox",
});
export async function requireEvmWalletControl(receiptId: string, origin: string, signature: string) {
const contract = adapter.issueActionContract({
action_type: "enable_protocol_access",
action_scope: "sandbox:protocol_access",
wallet_binding: "required",
});
if ("ok" in contract && contract.ok === false) return { allowed: false, reason: contract.reason };
const challenge = await issueEvmWalletChallenge({
origin,
partnerId: adapter.kit.options.partnerId,
policyId: adapter.kit.options.policyId,
policyVersion: adapter.kit.options.policyVersion ?? 1,
actionType: contract.action_type,
actionScope: contract.action_scope,
networkId: contract.network_context?.network_id ?? "evm_sandbox",
actionContractNonce: contract.nonce,
});
if ("ok" in challenge) return { allowed: false, reason: challenge.status };
// Holder signs challenge.message with personal_sign only.
// Copy: "Sign this message to prove control for this one action."
// Copy: "No transaction will be created or signed."
// Copy: "Abraxas does not read your balances or hold your keys."
const bound = await bindEvmWalletControl({
challengeId: challenge.challenge_id,
origin,
partnerId: adapter.kit.options.partnerId,
policyId: adapter.kit.options.policyId,
policyVersion: adapter.kit.options.policyVersion ?? 1,
actionType: contract.action_type,
actionScope: contract.action_scope,
networkId: contract.network_context?.network_id ?? "evm_sandbox",
actionContractNonce: contract.nonce,
message: challenge.message,
signature,
});
// Store only bound.binding_ref. Never log an address or signature.
const verified = await adapter.verifySignedReceipt(receiptId);
return adapter.preflight({
result: verified,
contract,
binding_ref: bound.binding_ref,
});
}
Studio: Integration Studio · EVM adapter: EVM partner adapter