mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
/login and /signup draw "Continue with wallet" and "Continue with phone" after Google and GitHub, each only where IAM's methods say it is on. Wallet: where IAM signs a wallet in (@hanzo/iam 0.21.37 `wallet`) and the browser injects an EIP-1193 wallet; the press is loginWithWallet (IAM's challenge, personal_sign of its EIP-4361 text, IAM's verify) and ends as a password's sign-in ends. A wallet IAM has not seen is made an account in an org of its own, with no email. Phone: where IAM says it sends codes (`code`) and has answered. The number is read in its country with the SMS consent ticked, then IAM's code flow. IAM's address lookup answers an email address only, so a number is texted as any is and the account that holds it is signed in by the code. Settings -> General -> Sign in another way adds an email or a phone to the signed-in account through IAM's own proof (addAddress, PUT /v1/iam/address), drawn only where IAM's methods say `address`. Same account, org, keys and balance; nothing is created, so nothing earns twice. Gates: wallet.spec (a throwaway secp256k1 key behind an injected provider), phone.spec, address.spec. login.spec expects the phone after the providers.
44 lines
1.9 KiB
TypeScript
44 lines
1.9 KiB
TypeScript
/**
|
|
* A wallet, as the sign-in panel and Settings see it. The sign-in itself is
|
|
* @hanzo/iam's `loginWithWallet`: IAM's challenge, the EIP-4361 text rendered
|
|
* from it, `personal_sign` by the browser's own wallet, and IAM's verify, which
|
|
* answers as its login does. This file answers the three questions the page asks
|
|
* around that call, and is pure, so each is tested without a browser.
|
|
*/
|
|
|
|
/** The EIP-1193 request surface `loginWithWallet` signs through. */
|
|
type Provider = { request?: unknown }
|
|
|
|
/**
|
|
* Whether this browser holds an Ethereum wallet the sign-in can ask: the injected
|
|
* EIP-1193 provider (`window.ethereum`) `loginWithWallet` signs with when it is
|
|
* handed none. No wallet, no button: one that could not finish is worse than none.
|
|
*/
|
|
export function injected(w: { ethereum?: Provider } | undefined = typeof window === 'undefined' ? undefined : (window as { ethereum?: Provider })): boolean {
|
|
return typeof w?.ethereum?.request === 'function'
|
|
}
|
|
|
|
/**
|
|
* Whether the wallet declined to sign: the person said no in its prompt, which
|
|
* EIP-1193 states as error 4001. Said in the page's words, not the wallet's.
|
|
*/
|
|
export function declined(err: unknown): boolean {
|
|
return typeof err === 'object' && err !== null && (err as { code?: unknown }).code === 4001
|
|
}
|
|
|
|
/** One wallet that signs an account in, as IAM names it: its chain family and its address. */
|
|
export type Wallet = { chain: string; address: string }
|
|
|
|
/**
|
|
* The wallets an access token says sign its account in: IAM's `wallets` claim, in
|
|
* IAM's order. Anything that is not a chain and an address is not one.
|
|
*/
|
|
export function wallets(claims: Record<string, unknown> | null | undefined): Wallet[] {
|
|
const set = claims?.wallets
|
|
if (!Array.isArray(set)) return []
|
|
return set.flatMap((w) => {
|
|
const r = (w ?? {}) as Record<string, unknown>
|
|
return typeof r.chain === 'string' && r.chain && typeof r.address === 'string' && r.address ? [{ chain: r.chain, address: r.address }] : []
|
|
})
|
|
}
|