Skip to content

Commit

Permalink
Add account discovery APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
heliuchuan committed Feb 10, 2025
1 parent ceb7918 commit b4f4b52
Show file tree
Hide file tree
Showing 11 changed files with 1,436 additions and 1,145 deletions.
35 changes: 34 additions & 1 deletion src/api/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@
// SPDX-License-Identifier: Apache-2.0

import { Account as AccountModule } from "../account";
import { AccountAddress, PrivateKey, AccountAddressInput, createObjectAddress } from "../core";
import {
AccountAddress,
PrivateKey,
AccountAddressInput,
createObjectAddress,
AccountPublicKey,
PublicKey,
} from "../core";
import {
AccountData,
AnyNumber,
GetAccountCoinsDataResponse,
GetAccountCollectionsWithOwnedTokenResponse,
GetAccountOwnedTokensFromCollectionResponse,
GetAccountOwnedTokensQueryResponse,
GetSignaturesResponse,

Check failure on line 20 in src/api/account.ts

View workflow job for this annotation

GitHub Actions / run-tests

'GetSignaturesResponse' is defined but never used
GetObjectDataQueryResponse,
LedgerVersionArg,
MoveModuleBytecode,
Expand All @@ -30,11 +38,13 @@ import {
getAccountOwnedObjects,
getAccountOwnedTokens,
getAccountOwnedTokensFromCollectionAddress,
getAccountsForPublicKey,
getAccountTokensCount,
getAccountTransactionsCount,
getInfo,
getModule,
getModules,
getPublicKeyFromAccountAddress,

Check failure on line 47 in src/api/account.ts

View workflow job for this annotation

GitHub Actions / run-tests

Module '"../internal/account"' has no exported member 'getPublicKeyFromAccountAddress'.

Check failure on line 47 in src/api/account.ts

View workflow job for this annotation

GitHub Actions / run-tests

Module '"../internal/account"' has no exported member 'getPublicKeyFromAccountAddress'.

Check failure on line 47 in src/api/account.ts

View workflow job for this annotation

GitHub Actions / run-tests

Module '"../internal/account"' has no exported member 'getPublicKeyFromAccountAddress'.

Check failure on line 47 in src/api/account.ts

View workflow job for this annotation

GitHub Actions / run-tests

Module '"../internal/account"' has no exported member 'getPublicKeyFromAccountAddress'.

Check failure on line 47 in src/api/account.ts

View workflow job for this annotation

GitHub Actions / run-tests

'getPublicKeyFromAccountAddress' is defined but never used
getResource,
getResources,
getTransactions,
Expand Down Expand Up @@ -894,4 +904,27 @@ export class Account {
async deriveAccountFromPrivateKey(args: { privateKey: PrivateKey }): Promise<AccountModule> {
return deriveAccountFromPrivateKey({ aptosConfig: this.config, ...args });
}

async getAccountsForPublicKey(args: {
publicKey: Exclude<AccountPublicKey, AbstractMultiKey>;

Check failure on line 909 in src/api/account.ts

View workflow job for this annotation

GitHub Actions / run-tests

Cannot find name 'AbstractMultiKey'.

Check failure on line 909 in src/api/account.ts

View workflow job for this annotation

GitHub Actions / run-tests

Cannot find name 'AbstractMultiKey'.

Check failure on line 909 in src/api/account.ts

View workflow job for this annotation

GitHub Actions / run-tests

Cannot find name 'AbstractMultiKey'.

Check failure on line 909 in src/api/account.ts

View workflow job for this annotation

GitHub Actions / run-tests

Cannot find name 'AbstractMultiKey'.
minimumLedgerVersion?: AnyNumber;
options?: { verified?: boolean };
}): Promise<
{
accountAddress: AccountAddress;
publicKey: PublicKey;
verified: boolean;
lastTransactionVersion: number;
}[]
> {
await waitForIndexerOnVersion({
config: this.config,
minimumLedgerVersion: args.minimumLedgerVersion,
processorType: ProcessorType.DEFAULT,
});
return getAccountsForPublicKey({
aptosConfig: this.config,
...args,
});
}
}
243 changes: 241 additions & 2 deletions src/internal/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ import { AptosConfig } from "../api/aptosConfig";
import { getAptosFullNode, paginateWithCursor, paginateWithObfuscatedCursor } from "../client";
import {
AccountData,
AnyPublicKeyVariant,
GetAccountCoinsDataResponse,
GetAccountCollectionsWithOwnedTokenResponse,
GetAccountOwnedTokensFromCollectionResponse,
GetAccountOwnedTokensQueryResponse,
GetMultiKeyForAuthKeyResponse,
GetObjectDataQueryResponse,
GetSignaturesResponse,

Check failure on line 22 in src/internal/account.ts

View workflow job for this annotation

GitHub Actions / run-tests

'GetSignaturesResponse' is defined but never used
isUserTransactionResponse,

Check failure on line 23 in src/internal/account.ts

View workflow job for this annotation

GitHub Actions / run-tests

'isUserTransactionResponse' is defined but never used
LedgerVersionArg,
MoveModuleBytecode,
MoveResource,
Expand All @@ -29,7 +33,18 @@ import {
} from "../types";
import { AccountAddress, AccountAddressInput } from "../core/accountAddress";
import { Account } from "../account";
import { AnyPublicKey, Ed25519PublicKey, PrivateKey } from "../core/crypto";
import {
AccountPublicKey,
AnyPublicKey,
Ed25519PublicKey,
FederatedKeylessPublicKey,
KeylessPublicKey,
MultiEd25519PublicKey,
MultiKey,
PrivateKey,
PublicKey,
Secp256k1PublicKey,
} from "../core/crypto";
import { queryIndexer } from "./general";
import {
GetAccountCoinsCountQuery,
Expand All @@ -40,6 +55,10 @@ import {
GetAccountOwnedTokensQuery,
GetAccountTokensCountQuery,
GetAccountTransactionsCountQuery,
GetMultiKeyForAuthKeyQuery,
GetAuthKeysForPublicKeyQuery,
GetAccountAddressesForAuthKeyQuery,
GetSignaturesQuery,

Check failure on line 61 in src/internal/account.ts

View workflow job for this annotation

GitHub Actions / run-tests

'GetSignaturesQuery' is defined but never used
} from "../types/generated/operations";
import {
GetAccountCoinsCount,
Expand All @@ -50,13 +69,19 @@ import {
GetAccountOwnedTokensFromCollection,
GetAccountTokensCount,
GetAccountTransactionsCount,
GetMultiKeyForAuthKey,
GetAuthKeysForPublicKey,
GetAccountAddressesForAuthKey,
GetSignatures,

Check failure on line 75 in src/internal/account.ts

View workflow job for this annotation

GitHub Actions / run-tests

'GetSignatures' is defined but never used
} from "../types/generated/queries";
import { memoizeAsync } from "../utils/memoize";
import { Secp256k1PrivateKey, AuthenticationKey, Ed25519PrivateKey, createObjectAddress } from "../core";
import { Secp256k1PrivateKey, AuthenticationKey, Ed25519PrivateKey, createObjectAddress, Hex } from "../core";
import { CurrentFungibleAssetBalancesBoolExp } from "../types/generated/types";
import { getTableItem } from "./table";
import { APTOS_COIN } from "../utils";
import { AptosApiError } from "../errors";
import { Deserializer } from "../bcs";
import { getTransactionByVersion } from "./transaction";

Check failure on line 84 in src/internal/account.ts

View workflow job for this annotation

GitHub Actions / run-tests

'getTransactionByVersion' is defined but never used

/**
* Retrieves account information for a specified account address.
Expand Down Expand Up @@ -815,3 +840,217 @@ export async function isAccountExist(args: { aptosConfig: AptosConfig; authKey:
throw new Error(`Error while looking for an account info ${accountAddress.toString()}`);
}
}

async function getMultiKeysForAuthenticationKeys(args: {
aptosConfig: AptosConfig;
authKeys: AuthenticationKey[];
}): Promise<{ authKey: AuthenticationKey; publicKey: MultiKey | MultiEd25519PublicKey }[]> {
const { aptosConfig, authKeys } = args;
if (authKeys.length === 0) {
throw new Error("No authentication keys provided");
}

const whereCondition: { auth_key: { _in: string[] } } = {
auth_key: { _in: authKeys.map((authKey) => authKey.toString()) },
};

const graphqlQuery = {
query: GetMultiKeyForAuthKey,
variables: {
where_condition: whereCondition,
},
};
const { auth_key_multikey_layout: data } = await queryIndexer<GetMultiKeyForAuthKeyQuery>({
aptosConfig,
query: graphqlQuery,
originMethod: "getMultiKeysForAuthenticationKeys",
});

const authKeyToMultiKey = new Map(data.map((entry) => [entry.auth_key, entry]));

const result: { authKey: AuthenticationKey; publicKey: MultiKey | MultiEd25519PublicKey }[] = [];
for (let i = 0; i < authKeys.length; i += 1) {
const entry = authKeyToMultiKey.get(authKeys[i].toString());
if (!entry) {
throw new Error(`Failed to find multikey for authentication key ${authKeys[i]}`);
}
const publicKey = extractMultiKeyFromData(entry);
result.push({ authKey: authKeys[i], publicKey });
}

return result;
}

function extractMultiKeyFromData(data: GetMultiKeyForAuthKeyResponse): MultiKey | MultiEd25519PublicKey {
const signaturesRequired = data.signatures_required;
const multikeyLayout = data.multikey_layout_with_prefixes;
const multikeyType = data.multikey_type;

if (multikeyType === "multi_ed25519") {
const ed25519PublicKeys: Array<Ed25519PublicKey> = [];
for (const key of multikeyLayout) {
ed25519PublicKeys.push(new Ed25519PublicKey(key));
}
return new MultiEd25519PublicKey({
publicKeys: ed25519PublicKeys,
threshold: signaturesRequired,
});
}
if (multikeyType === "multi_key") {
const publicKeys: Array<PublicKey> = [];
for (const key of multikeyLayout) {
const deserializer = Deserializer.fromHex(key);
const variantIndex = deserializer.deserializeUleb128AsU32();
let publicKey: PublicKey;
switch (variantIndex) {
case AnyPublicKeyVariant.Ed25519:
publicKey = new Ed25519PublicKey(deserializer.deserializeFixedBytes(32));
break;
case AnyPublicKeyVariant.Secp256k1:
publicKey = new Secp256k1PublicKey(deserializer.deserializeFixedBytes(65));
break;
case AnyPublicKeyVariant.Keyless:
publicKey = KeylessPublicKey.deserialize(deserializer);
break;
case AnyPublicKeyVariant.FederatedKeyless:
publicKey = FederatedKeylessPublicKey.deserialize(deserializer);
break;
default:
throw new Error(`Unknown variant index for AnyPublicKey: ${variantIndex}`);
}
publicKeys.push(new AnyPublicKey(publicKey));
}
return new MultiKey({
publicKeys,
signaturesRequired,
});
}
throw new Error("Unknown multikey type");
}

async function getAuthKeysForPublicKey(args: {
aptosConfig: AptosConfig;
publicKey: Exclude<AccountPublicKey, AbstractMultiKey>;
options?: { verified?: boolean };
}): Promise<AuthenticationKey[]> {
const { aptosConfig, publicKey, options } = args;
const verified = options?.verified ?? true;
let baseKey: PublicKey = publicKey;
if (publicKey instanceof AnyPublicKey) {
baseKey = publicKey.publicKey;
}
const whereCondition: any = {
public_key: { _eq: baseKey.toString() },
verified: { _eq: verified },
};
const graphqlQuery = {
query: GetAuthKeysForPublicKey,
variables: {
where_condition: whereCondition,
},
};
const { public_key_auth_keys: data } = await queryIndexer<GetAuthKeysForPublicKeyQuery>({
aptosConfig,
query: graphqlQuery,
originMethod: "getAuthKeysForPublicKey",
});

const sortedData = data.sort((a, b) => Number(b.last_transaction_version) - Number(a.last_transaction_version));
const authKeys = sortedData.map((entry) => new AuthenticationKey({ data: entry.auth_key }));
return authKeys;
}

async function getAccountAddressesForAuthKeys(args: {
aptosConfig: AptosConfig;
authKeys: AuthenticationKey[];
options?: { verified?: boolean };
}): Promise<
{ authKey: AuthenticationKey; accountAddress: AccountAddress; verified: boolean; lastTransactionVersion: number }[]
> {
const { aptosConfig, options, authKeys } = args;
const verified = options?.verified ?? true;
if (authKeys.length === 0) {
throw new Error("No authentication keys provided");
}

const whereCondition: any = {
auth_key: { _in: authKeys.map((authKey) => authKey.toString()) },
verified: { _eq: verified },
};

const graphqlQuery = {
query: GetAccountAddressesForAuthKey,
variables: {
where_condition: whereCondition,
},
};
const { auth_key_account_addresses: data } = await queryIndexer<GetAccountAddressesForAuthKeyQuery>({
aptosConfig,
query: graphqlQuery,
originMethod: "getAccountAddressesForAuthKeys",
});
return data.map((entry) => ({
authKey: new AuthenticationKey({ data: entry.auth_key }),
accountAddress: new AccountAddress(Hex.hexInputToUint8Array(entry.address)),
verified: entry.verified,
lastTransactionVersion: Number(entry.last_transaction_version),
}));
}

export async function getAccountsForPublicKey(args: {
aptosConfig: AptosConfig;
publicKey: Exclude<AccountPublicKey, AbstractMultiKey>;
}): Promise<
{ accountAddress: AccountAddress; publicKey: PublicKey; verified: boolean; lastTransactionVersion: number }[]
> {
const { aptosConfig, publicKey } = args;

let baseKey: PublicKey = publicKey;
if (publicKey instanceof AnyPublicKey) {
baseKey = publicKey.publicKey;
}
const singleKeyPublicKeys: AccountPublicKey[] = [];
if (baseKey instanceof Ed25519PublicKey) {
singleKeyPublicKeys.push(baseKey);
}
const anyPublicKey = new AnyPublicKey(baseKey);
singleKeyPublicKeys.push(anyPublicKey);

const singleKeyAuthKeyPublicKeyPairs = singleKeyPublicKeys.map((publicKey) => {

Check failure on line 1019 in src/internal/account.ts

View workflow job for this annotation

GitHub Actions / run-tests

'publicKey' is already declared in the upper scope on line 1006 column 24
const authKey = publicKey.authKey();
return { authKey, publicKey };
});

const multiKeyAuthKeys = await getAuthKeysForPublicKey({ aptosConfig, publicKey });

const [multiKeyPairs, authKeyAccountAddressPairs] = await Promise.all([
getMultiKeysForAuthenticationKeys({ aptosConfig, authKeys: multiKeyAuthKeys }),
getAccountAddressesForAuthKeys({
aptosConfig,
authKeys: multiKeyAuthKeys.concat(singleKeyAuthKeyPublicKeyPairs.map((pair) => pair.publicKey.authKey())),
}),
]);

const authKeyPublicKeyPairs = singleKeyAuthKeyPublicKeyPairs.concat(multiKeyPairs);

const result: {
accountAddress: AccountAddress;
publicKey: PublicKey;
verified: boolean;
lastTransactionVersion: number;
}[] = [];
const authKeyToPublicKey = new Map(authKeyPublicKeyPairs.map((pair) => [pair.authKey.toString(), pair.publicKey]));
for (const authKeyAccountAddressPair of authKeyAccountAddressPairs) {
if (!authKeyToPublicKey.has(authKeyAccountAddressPair.authKey.toString())) {
throw new Error(`No publicKey found for authentication key ${authKeyAccountAddressPair.authKey}.`);
}
result.push({
accountAddress: authKeyAccountAddressPair.accountAddress,
publicKey: authKeyToPublicKey.get(authKeyAccountAddressPair.authKey.toString()) as PublicKey,
verified: authKeyAccountAddressPair.verified,
lastTransactionVersion: authKeyAccountAddressPair.lastTransactionVersion,
});
}

return result;
}
8 changes: 8 additions & 0 deletions src/internal/queries/getAccountAddressForAuthKey.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
query getAccountAddressesForAuthKey($where_condition: auth_key_account_addresses_bool_exp) {
auth_key_account_addresses(where: $where_condition) {
auth_key
address
last_transaction_version
verified
}
}
9 changes: 9 additions & 0 deletions src/internal/queries/getAuthKeysForPublicKey.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
query getAuthKeysForPublicKey($where_condition: public_key_auth_keys_bool_exp) {
public_key_auth_keys(where: $where_condition) {
public_key
public_key_type
auth_key
last_transaction_version
verified
}
}
9 changes: 9 additions & 0 deletions src/internal/queries/getMultiKeyForAuthKey.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
query getMultiKeyForAuthKey($where_condition: auth_key_multikey_layout_bool_exp) {
auth_key_multikey_layout(where: $where_condition) {
auth_key
last_transaction_version
multikey_layout_with_prefixes
multikey_type
signatures_required
}
}
13 changes: 13 additions & 0 deletions src/internal/queries/getSignatures.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
query getSignatures($where_condition: signatures_bool_exp, $offset: Int, $limit: Int, $order_by: [signatures_order_by!]) {
signatures(where: $where_condition, offset: $offset, limit: $limit, order_by: $order_by ) {
signature
public_key
public_key_indices
type
signer
transaction_version
threshold
is_sender_primary
}
}

Loading

0 comments on commit b4f4b52

Please sign in to comment.