Keyless 集成指南
从高层来看,集成 Keyless 账户有三个步骤:
- **配置与 IdP 的 OpenID 集成。**dApp 向所选 IdP(如 Google)注册并获得
client_id。 - 安装 Aptos TypeScript SDK。
- 在应用客户端中集成 Keyless 账户支持。
- 为用户设置“使用 [IdP] 登录”流程。
- 实例化用户的
KeylessAccount。 - 通过
KeylessAccount签名并提交交易。
可在 aptos-keyless-example 仓库找到演示 Google 基础 Keyless 集成的示例应用。请按照 README 中的说明启动示例。有关 Keyless 的更详细说明,请继续阅读本集成指南。
-
第 1 步:配置与 IdP 的 OpenID 集成
首先配置 IdP。
-
第 2 步:安装 Aptos TypeScript SDK
Terminal window # Keyless is supported in version 1.18.1 and abovepnpm install @aptos-labs/ts-sdk -
第 3 步:客户端集成步骤
以下是客户端集成 Keyless 账户的默认步骤。
1. 在 UI 中向用户展示“使用 [IdP] 登录”按钮
Section titled “1. 在 UI 中向用户展示“使用 [IdP] 登录”按钮”-
在后台创建临时密钥对,并将其存储在 local storage 中。
import {EphemeralKeyPair} from '@aptos-labs/ts-sdk/keyless';const ephemeralKeyPair = EphemeralKeyPair.generate(); -
将
EphemeralKeyPair以其nonce为键存储在 local storage 中。// This saves the EphemeralKeyPair in local storagestoreEphemeralKeyPair(ephemeralKeyPair);
storeEphemeralKeyPair的示例实现/*** Store the ephemeral key pair in localStorage.*/export const storeEphemeralKeyPair = (ekp: EphemeralKeyPair): void =>localStorage.setItem("@aptos/ekp", encodeEphemeralKeyPair(ekp));/*** Retrieve the ephemeral key pair from localStorage if it exists.*/export const getLocalEphemeralKeyPair = (): EphemeralKeyPair | undefined => {try {const encodedEkp = localStorage.getItem("@aptos/ekp");return encodedEkp ? decodeEphemeralKeyPair(encodedEkp) : undefined;} catch (error) {console.warn("Failed to decode ephemeral key pair from localStorage",error);return undefined;}};/*** Stringify the ephemeral key pairs to be stored in localStorage*/export const encodeEphemeralKeyPair = (ekp: EphemeralKeyPair): string =>JSON.stringify(ekp, (_, e) => {if (typeof e === "bigint") return { __type: "bigint", value: e.toString() };if (e instanceof Uint8Array)return { __type: "Uint8Array", value: Array.from(e) };if (e instanceof EphemeralKeyPair)return { __type: "EphemeralKeyPair", data: e.bcsToBytes() };return e;});/*** Parse the ephemeral key pairs from a string*/export const decodeEphemeralKeyPair = (encodedEkp: string): EphemeralKeyPair =>JSON.parse(encodedEkp, (_, e) => {if (e && e.__type === "bigint") return BigInt(e.value);if (e && e.__type === "Uint8Array") return new Uint8Array(e.value);if (e && e.__type === "EphemeralKeyPair")return EphemeralKeyPair.fromBytes(e.data);return e;});-
准备登录 URL 的参数。将
redirect_uri和client_id设为 IdP 中配置的值。将nonce设为第 1.1 步中EphemeralKeyPair的 nonce。const redirectUri = 'https://.../login/callback'const clientId = env.IDP_CLIENT_ID// Get the nonce associated with ephemeralKeyPairconst nonce = ephemeralKeyPair.nonce -
构造登录 URL,让用户向 IdP 验证身份。务必设置
openidscope。可根据应用需求设置email、profile等其他 scope。const loginUrl = `https://accounts.google.com/o/oauth2/v2/auth?response_type=id_token&scope=openid+email+profile&nonce=${nonce}&redirect_uri=${redirectUri}&client_id=${clientId}` -
用户点击登录按钮时,将其重定向到第 1.4 步创建的
loginUrl。
2. 通过解析令牌处理回调,并为用户创建 Keyless 账户
Section titled “2. 通过解析令牌处理回调,并为用户创建 Keyless 账户”-
用户完成登录流程后,会被重定向到第 1 步设置的
redirect_uri。JWT 会作为 URL 片段中的搜索参数出现,键为id_token。按如下方式从window提取 JWT:const parseJWTFromURL = (url: string): string | null => {const urlObject = new URL(url);const fragment = urlObject.hash.substring(1);const params = new URLSearchParams(fragment);return params.get('id_token');};// window.location.href = https://.../login/google/callback#id_token=...const jwt = parseJWTFromURL(window.location.href) -
解码 JWT,并从载荷中提取 nonce 值。
import { jwtDecode } from 'jwt-decode';const payload = jwtDecode<{ nonce: string }>(jwt);const jwtNonce = payload.nonce -
获取第 1.2 步存储的
EphemeralKeyPair。务必验证 nonce 与解码的 nonce 相符,并且EphemeralKeyPair未过期。const ekp = getLocalEphemeralKeyPair();// Validate the EphemeralKeyPairif (!ekp || ekp.nonce !== jwtNonce || ekp.isExpired() ) {throw new Error("Ephemeral key pair not found or expired");} -
实例化用户的
KeylessAccount。根据所使用的 Keyless 类型,遵循以下说明:
- 普通 Keyless
import {Aptos, AptosConfig, Network} from '@aptos-labs/ts-sdk';const aptos = new Aptos(new AptosConfig({ network: Network.DEVNET })); // Configure your network hereconst keylessAccount = await aptos.deriveKeylessAccount({jwt,ephemeralKeyPair,});- 联邦 Keyless
import {Aptos, AptosConfig, Network} from '@aptos-labs/ts-sdk';const aptos = new Aptos(new AptosConfig({ network: Network.DEVNET })); // Configure your network hereconst keylessAccount = await aptos.deriveKeylessAccount({jwt,ephemeralKeyPair,jwkAddress: jwkOwner.accountAddress});
3. 将 KeylessAccount 存储到 local storage(可选)
Section titled “3. 将 KeylessAccount 存储到 local storage(可选)”-
派生账户后,将
KeylessAccount存储到 local storage。这样用户返回应用时无需再次验证。export const storeKeylessAccount = (account: KeylessAccount): void =>localStorage.setItem("@aptos/account", encodeKeylessAccount(account));export const encodeKeylessAccount = (account: KeylessAccount): string =>JSON.stringify(account, (_, e) => {if (typeof e === "bigint") return { __type: "bigint", value: e.toString() };if (e instanceof Uint8Array)return { __type: "Uint8Array", value: Array.from(e) };if (e instanceof KeylessAccount)return { __type: "KeylessAccount", data: e.bcsToBytes() };return e;}); -
每当用户返回应用时,从 local storage 中获取
KeylessAccount,并用它签名交易。export const getLocalKeylessAccount = (): KeylessAccount | undefined => {try {const encodedAccount = localStorage.getItem("@aptos/account");return encodedAccount ? decodeKeylessAccount(encodedAccount) : undefined;} catch (error) {console.warn("Failed to decode account from localStorage",error);return undefined;}};export const decodeKeylessAccount = (encodedAccount: string): KeylessAccount =>JSON.parse(encodedAccount, (_, e) => {if (e && e.__type === "bigint") return BigInt(e.value);if (e && e.__type === "Uint8Array") return new Uint8Array(e.value);if (e && e.__type === "KeylessAccount")return KeylessAccount.fromBytes(e.data);return e;});
4. 向 Aptos 区块链提交交易
Section titled “4. 向 Aptos 区块链提交交易”-
创建要提交的交易。以下是简单代币转账交易示例:
import {Account} from '@aptos-labs/ts-sdk';const bob = Account.generate();const transaction = await aptos.transferCoinTransaction({sender: keylessAccount.accountAddress,recipient: bob.accountAddress,amount: 100,}); -
签名并将交易提交到链上。
const committedTxn = await aptos.signAndSubmitTransaction({ signer: keylessAccount, transaction }); -
等待交易在链上处理。
const committedTransactionResponse = await aptos.waitForTransaction({ transactionHash: committedTxn.hash });
-