Skip to content
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ coverage
.next/
out/
build
dist/

# misc
.DS_Store
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-config-adapter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"eslint-config-turbo": "latest"
},
"devDependencies": {
"typescript": "^4.7.4"
"typescript": "^5.4.5"
},
"publishConfig": {
"access": "public"
Expand Down
2 changes: 1 addition & 1 deletion packages/wallet-adapter-ant-design/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"@types/react-dom": "^18.3.0",
"eslint": "^8.15.0",
"tsup": "^5.10.1",
"typescript": "^4.5.3"
"typescript": "^5.4.5"
},
"dependencies": {
"@ant-design/icons": "^5.3.7",
Expand Down
28 changes: 21 additions & 7 deletions packages/wallet-adapter-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,16 @@
"types": "./dist/index.d.ts",
"license": "Apache-2.0",
"exports": {
"require": "./dist/index.js",
"import": "./dist/index.mjs",
"types": "./dist/index.d.ts"
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
"import": "./dist/index.mjs"
},
"./new": {
"types": "./dist/new/index.d.ts",
"require": "./dist/new/index.js",
"import": "./dist/new/index.mjs"
}
},
"repository": {
"type": "git",
Expand All @@ -30,14 +37,21 @@
"scripts": {
"update-version": "node -p \"'export const WALLET_ADAPTER_CORE_VERSION = ' + JSON.stringify(require('./package.json').version) + ';'\" > src/version.ts",
"build-package": "export $(cat .env | xargs) && pnpm build:bundle && pnpm build:declarations",
"build:bundle": "tsup src/index.ts --format cjs,esm --sourcemap --env.GAID $GAID",
"build:bundle": "tsup --sourcemap --env.GAID $GAID",
"build:declarations": "tsc --emitDeclarationOnly --declaration --declarationMap",
"build": "pnpm run update-version && pnpm run build-package",
"dev": "export $(cat .env | xargs) && tsup src/index.ts --format esm,cjs --watch --dts --env.GAID $GAID",
"dev": "export $(cat .env | xargs) && tsup --watch --dts --env.GAID $GAID",
"test": "jest",
"lint": "TIMING=1 eslint \"src/**/*.ts*\"",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
},
"tsup": {
"entryPoints": [
"src/index.ts",
"src/new/index.ts"
],
"format": ["cjs", "esm"]
},
"devDependencies": {
"@aptos-labs/eslint-config-adapter": "workspace:*",
"@aptos-labs/wallet-adapter-tsconfig": "workspace:*",
Expand All @@ -46,8 +60,8 @@
"eslint": "^8.15.0",
"jest": "^29.3.1",
"ts-jest": "^29.0.3",
"tsup": "^5.10.1",
"typescript": "^4.5.3"
"tsup": "^8.3.6",
"typescript": "^5.4.5"
},
"dependencies": {
"@aptos-connect/wallet-adapter-plugin": "2.4.1",
Expand Down
52 changes: 52 additions & 0 deletions packages/wallet-adapter-core/src/new/AptosStandardWallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
AptosFeatures,
AptosWallet as AptosStandardWallet,
MinimallyRequiredFeatures,
Wallet,
} from '@aptos-labs/wallet-standard';

type FeatureVersion = `${number}.${number}` | `${number}.${number}.${number}`;
type TargetVersion = `${number}.${number}`;

/**
* Required features with minimum versions.
* In the future, we might choose to slowly deprecate older versions to simplify the adapter's code.
*/
const requiredFeatures: [name: keyof MinimallyRequiredFeatures, version: TargetVersion][] = [
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe put in the wallet-standard repo, to ease on maintenance

['aptos:account', '1.0'],
['aptos:connect', '1.0'],
['aptos:disconnect', '1.0'],
['aptos:network', '1.0'],
['aptos:onAccountChange', '1.0'],
['aptos:onNetworkChange', '1.0'],
['aptos:signMessage', '1.0'],
['aptos:signTransaction', '1.0'],
];

/**
* Check whether the specified version is compatible with a target version
*/
function isVersionCompatible(value: FeatureVersion, target: TargetVersion) {
const [major, minor] = value.split('.').map(Number);
const [tgtMajor, tgtMinor] = target.split('.').map(Number);
return major === tgtMajor && minor >= tgtMinor;
}

/**
* Check whether a generic wallet is an Aptos standard wallet.
*
* The wallet needs to implement all the required features with minimum version.
* @param wallet generic wallet to be considered compatible.
*/
export function isAptosStandardWallet(wallet: Wallet): wallet is AptosStandardWallet {
Copy link
Contributor

Choose a reason for hiding this comment

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

we kind of have it already in the standard repo https://github.com/aptos-labs/wallet-standard/blob/main/src/detect.ts#L27

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True, I just wanted to improve the logic without having to make a new release in the wallet-standard repo.
We should eventually move it over.

Copy link
Contributor

Choose a reason for hiding this comment

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

gotcha, lets leave a comment then, and move it there before releasing it

const features = wallet.features as Partial<AptosFeatures>;
for (const [name, targetVersion] of requiredFeatures) {
const feature = features[name];
if (!feature || !isVersionCompatible(feature.version, targetVersion)) {
return false;
}
}
return true;
}

export type { AptosStandardWallet };
259 changes: 259 additions & 0 deletions packages/wallet-adapter-core/src/new/WalletAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
import { Aptos, AptosConfig } from '@aptos-labs/ts-sdk';
import {
AccountInfo,
AptosFeatures,
AptosSignAndSubmitTransactionInput,
AptosSignAndSubmitTransactionOutput,
AptosSignMessageInput,
AptosSignTransactionInputV1_1,
AptosSignTransactionOutput,
AptosSignTransactionOutputV1_1,
UserResponseStatus,
WalletIcon,
} from '@aptos-labs/wallet-standard';
import { GA4 } from '../ga';
import { WALLET_ADAPTER_CORE_VERSION } from '../version';
import { AptosStandardWallet } from './AptosStandardWallet';
import { Network, StandardNetwork } from './network';
import {
aptosChainIdentifierToNetworkMap,
buildTransaction,
chainIdToStandardNetwork,
isFeatureMinorVersion,
mapUserResponse,
networkInfoToNetwork,
} from './utils';

// The standard doesn't currently allow removing event listeners, so instead
// we pass a special null callback that can be detected by wallets as "null"
const nullCallback = Object.assign(() => {
}, { _isNull: true });

type EventHandlers = {
accountConnected: (account: AccountInfo) => void;
accountDisconnected: (account?: AccountInfo) => void;
activeAccountChanged: (account?: AccountInfo) => void;
activeNetworkChanged: (network?: Network) => void;
}

export interface WalletAdapterConfig {
disableTelemetry?: boolean;
}

/**
* A wallet instance adapted from an Aptos standard wallet that supports
* all required features with minimum version.
*/
export class WalletAdapter {
readonly name: string;
readonly url: string;
readonly icon: WalletIcon;
readonly features: AptosFeatures;

readonly availableNetworks: Network[];

// Google Analytics 4 module
private readonly ga4?: GA4;

private _activeNetwork?: Network;

constructor(wallet: AptosStandardWallet, options: WalletAdapterConfig = {}) {
this.name = wallet.name;
this.url = wallet.url;
this.icon = wallet.icon;
this.features = wallet.features;

if (!options.disableTelemetry) {
this.ga4 = new GA4();
}

this.availableNetworks = [];
for (const chain of wallet.chains) {
const network = aptosChainIdentifierToNetworkMap[chain];
if (network) {
this.availableNetworks.push(network);
}
}
}

// TODO: revise event formats and names
private recordEvent(eventName: string, additionalInfo?: object) {
this.ga4?.gtag("event", `wallet_adapter_${eventName}`, {
wallet: this.name,
activeNetwork: this._activeNetwork,
adapter_core_version: WALLET_ADAPTER_CORE_VERSION,
send_to: process.env.GAID,
...additionalInfo,
});
}

// region Connection

async connect() {
const feature = this.features['aptos:connect'];
const response = await feature.connect();
if (response.status === UserResponseStatus.APPROVED) {
for (const callback of this.onAccountConnectedListeners) {
callback(response.args);
}
}
return response;
}

async disconnect() {
// TODO: specify which account. defaults to active account
const feature = this.features['aptos:disconnect'];
await feature.disconnect();
for (const callback of this.onAccountDisconnectedListeners) {
callback();
}
}

// endregion

// region Accounts

private readonly onAccountConnectedListeners = new Set<(account: AccountInfo) => void>();
private readonly onAccountDisconnectedListeners = new Set<() => void>();

async getConnectedAccounts(): Promise<AccountInfo[]> {
// TODO: add explicit `getConnectedAccounts` feature
const activeAccount = await this.getActiveAccount();
return activeAccount ? [activeAccount] : [];
}

onAccountConnected(callback: (account: AccountInfo) => void) {
this.onAccountConnectedListeners.add(callback);
return () => this.onAccountConnectedListeners.delete(callback);
}

onAccountDisconnected(callback: () => void) {
this.onAccountDisconnectedListeners.add(callback);
return () => this.onAccountDisconnectedListeners.delete(callback);
}

async getActiveAccount(): Promise<AccountInfo | undefined> {
return this.features['aptos:account'].account()
.catch(() => undefined);
}

onActiveAccountChanged(callback: (account?: AccountInfo) => void) {
const feature = this.features['aptos:onAccountChange'];
void feature.onAccountChange((newAccount) => {
callback(newAccount);
});
return () => {
void feature.onAccountChange(nullCallback);
}
}

// endregion

// region Networks

async getAvailableNetworks(): Promise<Network[]> {
// TODO: maybe add explicit `getAvailableNetworks` feature
return this.availableNetworks;
}

async getActiveNetwork(): Promise<Network> {
const feature = this.features['aptos:network'];
try {
const networkInfo = await feature.network();
const network = networkInfoToNetwork(networkInfo);
this._activeNetwork = network;
return network;
} catch {
this._activeNetwork = StandardNetwork.MAINNET;
return StandardNetwork.MAINNET;
}
}

onActiveNetworkChanged(callback: (network?: Network) => void) {
const feature = this.features['aptos:onNetworkChange'];

void feature.onNetworkChange((networkInfo) => {
const network = networkInfo && networkInfoToNetwork(networkInfo);
this._activeNetwork = network;
callback(network);
});
return () => {
void feature.onNetworkChange(nullCallback);
}
}

// endregion

// region Signature

// TODO: improve message signature standard
async signMessage(input: AptosSignMessageInput) {
const feature = this.features['aptos:signMessage'];
return feature.signMessage(input);
}

async signTransaction(input: AptosSignTransactionInputV1_1) {
const feature = this.features['aptos:signTransaction']

if (isFeatureMinorVersion(feature, "1.0")) {
const { signerAddress, feePayer } = input;
// This will throw an error if it requires an async call
const transaction = buildTransaction(input);
const asFeePayer = signerAddress?.toString() === feePayer?.address.toString();
const response = await feature.signTransaction(transaction, asFeePayer);

return mapUserResponse<AptosSignTransactionOutput, AptosSignTransactionOutputV1_1>(
response, (authenticator) => ({
authenticator,
rawTransaction: transaction,
}));
}

return feature.signTransaction(input);
}

async signAndSubmitTransaction(input: AptosSignAndSubmitTransactionInput) {
const feature = this.features['aptos:signAndSubmitTransaction']
if (feature) {
return feature.signAndSubmitTransaction(input);
}

const response = await this.signTransaction(input);
return mapUserResponse<AptosSignTransactionOutputV1_1, AptosSignAndSubmitTransactionOutput>(
response, async ({ rawTransaction: transaction, authenticator }) => {
const { chainId } = transaction.rawTransaction.chain_id;
const network = chainIdToStandardNetwork(chainId);
const aptosConfig = new AptosConfig({ network });
const aptosClient = new Aptos(aptosConfig);

const { hash } = await aptosClient.transaction.submit.simple({
transaction,
senderAuthenticator: authenticator,
});
return { hash };
});
}

// endregion

// region Event handling

on<EventName extends keyof EventHandlers>(eventName: EventName, callback: EventHandlers[EventName]) {
const handlers: {
[K in keyof EventHandlers]: (cb: EventHandlers[K]) => () => void;
} = {
accountConnected: (cb) => this.onAccountConnected(cb),
accountDisconnected: (cb) => this.onAccountDisconnected(cb),
activeAccountChanged: (cb) => this.onActiveAccountChanged(cb),
activeNetworkChanged: (cb) => this.onActiveNetworkChanged(cb),
};

const handler = handlers[eventName];
if (!handler) {
throw new Error('Unsupported event name');
}
return handler(callback);
}

// endregion
}
Loading