Developers · Wallet Standard binding
Optionally bind a wallet to one action contract
Wallet binding proves control of a self-custodial wallet for one action contract. It is not identity verification and does not reveal private wallet information. Connection is optional. Passport, Partner Flow, and receipt verification work with no wallet connected. Abraxas never generates a transaction, reads balances, or holds keys.
When to use it
Use binding when a trading venue, payment, or membership check needs to know that the same self-custodial wallet still controls a later action. Do not use it as login, KYC, or a substitute for a signed eligibility receipt.
Use Wallet Standard signMessage. Phantom is supported through that standard. Do not request transaction signing or extra wallet permissions.
Durable store migration
This public feature requires 092_wallet_standard_action_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 and opaque binding_ref values. Never signatures, addresses, seed phrases, balances, or transactions.
- Confirm you are targeting the isolated DEMO project, not Production.
- Open the DEMO SQL editor and paste the full contents of supabase/migrations/092_wallet_standard_action_bindings.sql.
- Run the script once. It is idempotent.
- Confirm tables wallet_standard_challenges, wallet_standard_bindings, and partner_venue_action_nonces exist.
- Confirm RLS is enabled and anon/authenticated have no grants.
- Do not paste service-role keys, HMAC secrets, or wallet material into tickets or docs.
- Apply 092 only after DEMO apply succeeded and this feature is approved for Production.
- Open the Production SQL editor and paste the same 092 file. Do not reuse DEMO connection strings.
- Run the script once. It is idempotent.
- Reload the PostgREST schema cache.
- Missing 092 must fail closed with store_unavailable. There is no memory fallback.
Partner implementation
// 1. Issue a domain-bound challenge on your server. Do not put a wallet address in the request.
const challenge = await fetch("/api/wallet-standard/challenge", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
partner_id: process.env.ABRAXAS_PARTNER_ID,
action_contract_nonce: actionContract.nonce,
}),
}).then((res) => res.json());
// 2. Ask the holder to signMessage through Wallet Standard (Phantom compatible).
// Do not create a transaction. Do not request balances or extra permissions.
import { signWalletStandardChallenge } from "@/lib/partner/walletStandard/connector";
const signed = await signWalletStandardChallenge(challenge.message);
// 3. Bind on the server. Store only the opaque binding_ref.
const bound = await fetch("/api/wallet-standard/bind", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
challenge_id: challenge.challenge_id,
partner_id: process.env.ABRAXAS_PARTNER_ID,
action_contract_nonce: actionContract.nonce,
message: challenge.message,
signature: signed.ok ? signed.signature : "",
public_key: signed.ok ? signed.publicKey : "",
}),
}).then((res) => res.json());
// bound.binding_ref is tenant-scoped. Never log public_key or an address.
// 4. Optional or required preflight on the Trading Venue Adapter.
await adapter.preflight({
result: verifiedReceipt,
contract: { ...actionContract, wallet_binding: "optional" },
binding_ref: bound.binding_ref,
});
Studio: Integration Studio · Venue adapter: Trading venue adapter