Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor the account info output type to be compatible with AIP-62 standard #441

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/nervous-mayflies-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@aptos-labs/wallet-adapter-react": major
"@aptos-labs/wallet-adapter-core": major
"@aptos-labs/wallet-adapter-nextjs-example": major
---

Refactor the account info output type to be compatible with AIP-62
9 changes: 6 additions & 3 deletions apps/nextjs-example/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { WalletSelector as AntdWalletSelector } from "@aptos-labs/wallet-adapter
import { WalletConnector as MuiWalletSelector } from "@aptos-labs/wallet-adapter-mui-design";
import {
AccountInfo,
AccountInfoOutput,
AptosChangeNetworkOutput,
NetworkInfo,
WalletInfo,
Expand All @@ -48,7 +49,9 @@ import { registerWallet } from "@aptos-labs/wallet-standard";
registerWallet(myWallet);
})();

const isTelegramMiniApp = typeof window !== 'undefined' && (window as any).TelegramWebviewProxy !== undefined;
const isTelegramMiniApp =
typeof window !== "undefined" &&
(window as any).TelegramWebviewProxy !== undefined;
if (isTelegramMiniApp) {
initTelegram();
}
Expand Down Expand Up @@ -147,7 +150,7 @@ function WalletSelection() {
}

interface WalletConnectionProps {
account: AccountInfo | null;
account: AccountInfoOutput | null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use AccountInfo from the standard

network: NetworkInfo | null;
wallet: WalletInfo | null;
changeNetwork: (network: Network) => Promise<AptosChangeNetworkOutput>;
Expand Down Expand Up @@ -225,7 +228,7 @@ function WalletConnection({
label: "Address",
value: (
<DisplayValue
value={account?.address ?? "Not Present"}
value={account?.address.toString() ?? "Not Present"}
isCorrect={!!account?.address}
/>
),
Expand Down
2 changes: 1 addition & 1 deletion apps/nextjs-example/src/components/WalletSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function WalletSelector(walletSortingOptions: WalletSortingOptions) {
const copyAddress = useCallback(async () => {
if (!account?.address) return;
try {
await navigator.clipboard.writeText(account.address);
await navigator.clipboard.writeText(account.address.toString());
toast({
title: "Success",
description: "Copied wallet address to clipboard.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export function SingleSigner() {
data: {
function: "0x1::coin::transfer",
typeArguments: [parseTypeTag(APTOS_COIN)],
functionArguments: [AccountAddress.from(account.address), new U64(1)], // 1 is in Octas
functionArguments: [account.address, new U64(1)], // 1 is in Octas
},
});
await aptosClient(network).waitForTransaction({
Expand All @@ -99,7 +99,7 @@ export function SingleSigner() {
type: "entry_function_payload",
function: "0x1::coin::transfer",
type_arguments: ["0x1::aptos_coin::AptosCoin"],
arguments: [account?.address, 1], // 1 is in Octas
arguments: [account?.address.toString(), 1], // 1 is in Octas
};
const response = await signTransaction(payload);
toast({
Expand All @@ -116,13 +116,13 @@ export function SingleSigner() {

try {
const transactionToSign = await aptosClient(
network,
network
).transaction.build.simple({
sender: account.address,
data: {
function: "0x1::coin::transfer",
typeArguments: [APTOS_COIN],
functionArguments: [account.address, 1], // 1 is in Octas
functionArguments: [account.address.toString(), 1], // 1 is in Octas
},
});
const response = await signTransaction(transactionToSign);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,11 @@ export class WalletStandardCore {
transaction: AnyRawTransaction,
wallet: Wallet,
asFeePayer?: boolean
): Promise<AptosSignTransactionOutput>
): Promise<AptosSignTransactionOutput>;
async signTransaction(
input: AptosSignTransactionInputV1_1,
wallet: Wallet,
): Promise<AptosSignTransactionOutputV1_1>
wallet: Wallet
): Promise<AptosSignTransactionOutputV1_1>;
async signTransaction(
transactionOrInput: AnyRawTransaction | AptosSignTransactionInputV1_1,
wallet: Wallet,
Expand Down
16 changes: 16 additions & 0 deletions packages/wallet-adapter-core/src/AIP62StandardWallets/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AccountAddress, PublicKey } from "@aptos-labs/ts-sdk";
import { WalletName } from "../LegacyWalletPlugins/types";
import { WalletReadyState } from "../constants";

Expand All @@ -16,6 +17,21 @@ export interface AptosStandardSupportedWallet<Name extends string = string> {
isAIP62Standard: true;
}

export type StandardAccountInfoInput = {
address: AccountAddress;
publicKey: PublicKey;
ansName?: string;
};

// Output type for the account to support AIP-62 standard wallets
export type AccountInfoOutput = {
address: AccountAddress;
// PublicKey[] for backward compatibility,
publicKey: PublicKey | PublicKey[];
minKeysRequired?: number;
ansName?: string | null;
};

// Update this with the name of your wallet when you create a new wallet plugin.
export type AvailableWallets =
| "Nightly"
Expand Down
11 changes: 8 additions & 3 deletions packages/wallet-adapter-core/src/LegacyWalletPlugins/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
InputGenerateTransactionPayloadData,
AnyRawTransaction,
Signature,
AccountAuthenticator,
AccountAddress,
PublicKey,
} from "@aptos-labs/ts-sdk";
import { WalletReadyState } from "../constants";
import {
Expand All @@ -20,7 +21,10 @@ import {
AptosChangeNetworkMethod,
AptosSignAndSubmitTransactionInput,
} from "@aptos-labs/wallet-standard";
import { AptosStandardSupportedWallet } from "../AIP62StandardWallets/types";
import {
AptosStandardSupportedWallet,
StandardAccountInfoInput,
} from "../AIP62StandardWallets/types";

export { TxnBuilderTypes, Types } from "aptos";
export type {
Expand Down Expand Up @@ -55,6 +59,7 @@ export type WalletInfo = {
url: string;
};

// Input type to handle legacy wallets that are not AIP-62 compatible
export type AccountInfo = {
address: string;
publicKey: string | string[];
Expand All @@ -74,7 +79,7 @@ export declare interface WalletCoreEvents {
readyStateChange(wallet: Wallet): void;
standardWalletsAdded(wallets: Wallet | AptosStandardSupportedWallet): void;
networkChange(network: NetworkInfo | null): void;
accountChange(account: AccountInfo | null): void;
accountChange(account: AccountInfo | StandardAccountInfoInput | null): void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it might be better to just share the AccountInfoOutput instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is tricky as the adapter still supports legacy and new standard types, so the account the adapter gets back from the wallet can be of both types. Changing it gets us into a type-rabbit-hole, so we simply change the actual input type the adapter returns.

In the near future, we should probably support only AIP-62 wallets

Copy link
Contributor

@hardsetting hardsetting Oct 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use AccountInfo from @aptos-labs/wallet-standard here, that uses the PublicKey type.

If I understand correctly, this is an event that can be listened to externally by the dapp.
Since we're bumping the major, we might as well clean up the interface and have the "right" type here.

In case of legacy plugins, we should be able to adapt the return types to AccountInfo so that the dapp sees one consistent return type.

EDIT: here's a good spot where we could normalize

}

export interface SignMessagePayload {
Expand Down
88 changes: 32 additions & 56 deletions packages/wallet-adapter-core/src/WalletCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@ import {
Ed25519PublicKey,
InputGenerateTransactionOptions,
Ed25519Signature,
AptosConfig,
InputSubmitTransactionData,
PendingTransactionResponse,
Aptos,
generateRawTransaction,
SimpleTransaction,
NetworkToChainId,
Hex,
PublicKey,
} from "@aptos-labs/ts-sdk";
import EventEmitter from "eventemitter3";
import {
Expand Down Expand Up @@ -60,7 +59,6 @@ import {
WalletName,
WalletCoreV1,
CompatibleTransactionOptions,
convertNetwork,
convertPayloadInputV1ToV2,
generateTransactionPayloadFromV1Input,
} from "./LegacyWalletPlugins";
Expand All @@ -80,6 +78,8 @@ import {
WalletStandardCore,
AptosStandardSupportedWallet,
AvailableWallets,
StandardAccountInfoInput,
AccountInfoOutput,
} from "./AIP62StandardWallets";
import { GA4 } from "./ga";
import { WALLET_ADAPTER_CORE_VERSION } from "./version";
Expand Down Expand Up @@ -131,7 +131,7 @@ export class WalletCore extends EventEmitter<WalletCoreEvents> {
private _wallet: Wallet | null = null;

// Current connected account
private _account: AccountInfo | null = null;
private _account: AccountInfo | StandardAccountInfoInput | null = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, let's just use AccountInfo from the standard. We can convert it to the right type before setting its value


// Current connected network
private _network: NetworkInfo | null = null;
Expand Down Expand Up @@ -465,7 +465,7 @@ export class WalletCore extends EventEmitter<WalletCoreEvents> {
* @param account An account
*/
private ensureAccountExists(
account: AccountInfo | null
account: AccountInfo | StandardAccountInfoInput | null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

): asserts account is AccountInfo {
if (!account) {
throw new WalletAccountError("Account is not set").name;
Expand Down Expand Up @@ -541,57 +541,12 @@ export class WalletCore extends EventEmitter<WalletCoreEvents> {
* Sets the connected account
*
* `AccountInfo` type comes from a legacy wallet adapter plugin
* `StandardAccountInfo` type comes from AIP-62 standard compatible wallet when onAccountChange event is called
* `UserResponse<StandardAccountInfo>` type comes from AIP-62 standard compatible wallet on wallet connect
* `StandardAccountInfo` type comes from AIP-62 standard compatible wallet
*
* @param account An account
*/
setAccount(
account:
| AccountInfo
| StandardAccountInfo
| UserResponse<StandardAccountInfo>
| null
): void {
if (account === null) {
this._account = null;
return;
}

// Check if wallet is of type AIP-62 standard
if (this._wallet?.isAIP62Standard) {
// Check if account is of type UserResponse<StandardAccountInfo> which means the `account`
// comes from the `connect` method
if ("status" in account) {
const connectStandardAccount =
account as UserResponse<StandardAccountInfo>;
if (connectStandardAccount.status === UserResponseStatus.REJECTED) {
this._connecting = false;
throw new WalletConnectionError("User has rejected the request")
.message;
}
// account is of type
this._account = {
address: connectStandardAccount.args.address.toString(),
publicKey: connectStandardAccount.args.publicKey.toString(),
ansName: connectStandardAccount.args.ansName,
};
return;
} else {
// account is of type `StandardAccountInfo` which means it comes from onAccountChange event
const standardAccount = account as StandardAccountInfo;
this._account = {
address: standardAccount.address.toString(),
publicKey: standardAccount.publicKey.toString(),
ansName: standardAccount.ansName,
};
return;
}
}

// account is of type `AccountInfo`
this._account = { ...(account as AccountInfo) };
return;
setAccount(account: AccountInfo | StandardAccountInfo | null): void {
this._account = account;
}
Comment on lines +548 to 550
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be a good spot where we support both AccountInfos (the old internal type, and the standard type) as inputs, and normalize it to the standard one.


/**
Expand Down Expand Up @@ -687,9 +642,30 @@ export class WalletCore extends EventEmitter<WalletCoreEvents> {
* @return account info
* @throws WalletAccountError
*/
get account(): AccountInfo | null {
get account(): AccountInfoOutput | null {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's just return AccountInfo from the wallet standard package

try {
return this._account;
if (!this._account) return null;
return {
address:
typeof this._account.address === "string"
? AccountAddress.from(this._account.address)
: this._account.address,
publicKey:
// for backward compatibility, if the account public key is of type `string` convert it to Ed25519PublicKey
typeof this._account.publicKey === "string"
? new Ed25519PublicKey(this._account.publicKey)
GhostWalker562 marked this conversation as resolved.
Show resolved Hide resolved
: // for backward compatibility, if the account public key is of type `string[]` convert each string to Ed25519PublicKey
Array.isArray(this._account.publicKey) &&
this._account.publicKey.every(
(item) => typeof item === "string"
)
? this._account.publicKey.map((key) => new Ed25519PublicKey(key))
Comment on lines +657 to +662
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we should convert string[] and minKeysRequired to MultiEd25519PublicKey that extends PublicKey

: // else, the account is of the new standard and return it with the exact type
(this._account.publicKey as PublicKey),
minKeysRequired:
(this._account as AccountInfo).minKeysRequired ?? undefined,
ansName: this._account.ansName,
};
} catch (error: any) {
throw new WalletAccountError(error).message;
}
Expand Down Expand Up @@ -931,7 +907,7 @@ export class WalletCore extends EventEmitter<WalletCoreEvents> {
if (this._wallet.signTransaction) {
// If current connected wallet is AIP-62 standard compatible
// we want to make sure the transaction input is what the
// standard expects, i,e new sdk v2 input
// standard expects, i.e new sdk v2 input
if (this._wallet.isAIP62Standard) {
// if rawTransaction prop it means transaction input data is
// compatible with new sdk v2 input
Expand Down
5 changes: 3 additions & 2 deletions packages/wallet-adapter-core/src/utils/walletSelector.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AccountAddress } from "@aptos-labs/ts-sdk";
import { AptosStandardWallet } from "../AIP62StandardWallets";
import { WalletInfo } from "../LegacyWalletPlugins";
import { AnyAptosWallet } from "../WalletCore";
Expand Down Expand Up @@ -44,9 +45,9 @@ export function isInstallRequired(wallet: AnyAptosWallet) {
}

/** Truncates the provided wallet address at the middle with an ellipsis. */
export function truncateAddress(address: string | undefined) {
export function truncateAddress(address: AccountAddress | undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small nit: we could use AccountAddressInput, but nbd

if (!address) return;
return `${address.slice(0, 6)}...${address.slice(-5)}`;
return `${address.toString().slice(0, 6)}...${address.toString().slice(-5)}`;
}

/** Returns `true` if the provided wallet is an Aptos Connect wallet. */
Expand Down
3 changes: 2 additions & 1 deletion packages/wallet-adapter-react/src/WalletProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {
Network,
AptosStandardSupportedWallet,
AvailableWallets,
AccountInfoOutput,
} from "@aptos-labs/wallet-adapter-core";
import { DappConfig, WalletCore } from "@aptos-labs/wallet-adapter-core";

Expand All @@ -32,7 +33,7 @@ export interface AptosWalletProviderProps {
}

const initialState: {
account: AccountInfo | null;
account: AccountInfoOutput | null;
network: NetworkInfo | null;
connected: boolean;
wallet: WalletInfo | null;
Expand Down
3 changes: 2 additions & 1 deletion packages/wallet-adapter-react/src/useWallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ import {
AptosChangeNetworkOutput,
Network,
AptosStandardSupportedWallet,
AccountInfoOutput,
} from "@aptos-labs/wallet-adapter-core";
import { createContext, useContext } from "react";

export interface WalletContextState {
connected: boolean;
isLoading: boolean;
account: AccountInfo | null;
account: AccountInfoOutput | null;
network: NetworkInfo | null;
connect(walletName: WalletName): void;
disconnect(): void;
Expand Down
Loading