From c60ad8bf3d2b1dfcc784568dba0c7714ef362f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B3nio=20Regadas?= Date: Tue, 5 Nov 2024 15:15:24 +0000 Subject: [PATCH] feat: adds Solana account type to keyring API (#82) We are adding the `SolAccountType` to support Solana account creation. --------- Co-authored-by: Charly Chevalier --- packages/keyring-api/src/api/account.ts | 11 ++++- packages/keyring-api/src/eth/utils.test.ts | 3 +- packages/keyring-api/src/index.ts | 1 + packages/keyring-api/src/internal/types.ts | 25 +++++++++-- packages/keyring-api/src/sol/index.ts | 1 + packages/keyring-api/src/sol/types.test-d.ts | 7 ++++ packages/keyring-api/src/sol/types.test.ts | 28 +++++++++++++ packages/keyring-api/src/sol/types.ts | 44 ++++++++++++++++++++ 8 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 packages/keyring-api/src/sol/index.ts create mode 100644 packages/keyring-api/src/sol/types.test-d.ts create mode 100644 packages/keyring-api/src/sol/types.test.ts create mode 100644 packages/keyring-api/src/sol/types.ts diff --git a/packages/keyring-api/src/api/account.ts b/packages/keyring-api/src/api/account.ts index 28b7d1e1..68a48524 100644 --- a/packages/keyring-api/src/api/account.ts +++ b/packages/keyring-api/src/api/account.ts @@ -20,13 +20,21 @@ export enum BtcAccountType { P2wpkh = 'bip122:p2wpkh', } +/** + * Supported Solana account types. + */ +export enum SolAccountType { + DataAccount = 'solana:data-account', +} + /** * Supported account types. */ export type KeyringAccountType = | `${EthAccountType.Eoa}` | `${EthAccountType.Erc4337}` - | `${BtcAccountType.P2wpkh}`; + | `${BtcAccountType.P2wpkh}` + | `${SolAccountType.DataAccount}`; /** * A struct which represents a Keyring account object. It is abstract enough to @@ -48,6 +56,7 @@ export const KeyringAccountStruct = object({ `${EthAccountType.Eoa}`, `${EthAccountType.Erc4337}`, `${BtcAccountType.P2wpkh}`, + `${SolAccountType.DataAccount}`, ]), /** diff --git a/packages/keyring-api/src/eth/utils.test.ts b/packages/keyring-api/src/eth/utils.test.ts index 3881aea9..54b78f29 100644 --- a/packages/keyring-api/src/eth/utils.test.ts +++ b/packages/keyring-api/src/eth/utils.test.ts @@ -1,11 +1,12 @@ import { isEvmAccountType } from './utils'; -import { BtcAccountType, EthAccountType } from '../api'; +import { BtcAccountType, EthAccountType, SolAccountType } from '../api'; describe('isEvmAccountType', () => { it.each([ [EthAccountType.Eoa, true], [EthAccountType.Erc4337, true], [BtcAccountType.P2wpkh, false], + [SolAccountType.DataAccount, false], [{}, false], [null, false], ['bitcoin', false], diff --git a/packages/keyring-api/src/index.ts b/packages/keyring-api/src/index.ts index 57920bb6..a5f24e68 100644 --- a/packages/keyring-api/src/index.ts +++ b/packages/keyring-api/src/index.ts @@ -2,6 +2,7 @@ export * from './api'; export * from './btc'; export type * from './contexts'; export * from './eth'; +export * from './sol'; export * from './events'; export * from './internal'; export * from './KeyringClient'; diff --git a/packages/keyring-api/src/internal/types.ts b/packages/keyring-api/src/internal/types.ts index bbf54a52..401cc649 100644 --- a/packages/keyring-api/src/internal/types.ts +++ b/packages/keyring-api/src/internal/types.ts @@ -1,12 +1,21 @@ import type { Infer, Struct } from '@metamask/superstruct'; import { boolean, string, number } from '@metamask/superstruct'; -import { BtcAccountType, EthAccountType, KeyringAccountStruct } from '../api'; +import { + BtcAccountType, + EthAccountType, + KeyringAccountStruct, + SolAccountType, +} from '../api'; import { BtcP2wpkhAccountStruct } from '../btc/types'; import { EthEoaAccountStruct, EthErc4337AccountStruct } from '../eth/types'; +import { SolDataAccountStruct } from '../sol/types'; import { exactOptional, object } from '../superstruct'; -export type InternalAccountType = EthAccountType | BtcAccountType; +export type InternalAccountType = + | EthAccountType + | BtcAccountType + | SolAccountType; export const InternalAccountMetadataStruct = object({ metadata: object({ @@ -42,6 +51,11 @@ export const InternalBtcP2wpkhAccountStruct = object({ ...InternalAccountMetadataStruct.schema, }); +export const InternalSolDataAccountStruct = object({ + ...SolDataAccountStruct.schema, + ...InternalAccountMetadataStruct.schema, +}); + export type InternalEthEoaAccount = Infer; export type InternalEthErc4337Account = Infer< @@ -52,21 +66,26 @@ export type InternalBtcP2wpkhAccount = Infer< typeof InternalBtcP2wpkhAccountStruct >; +export type InternalSolDataAccount = Infer; + export const InternalAccountStructs: Record< string, | Struct | Struct | Struct + | Struct > = { [`${EthAccountType.Eoa}`]: InternalEthEoaAccountStruct, [`${EthAccountType.Erc4337}`]: InternalEthErc4337AccountStruct, [`${BtcAccountType.P2wpkh}`]: InternalBtcP2wpkhAccountStruct, + [`${SolAccountType.DataAccount}`]: InternalSolDataAccountStruct, }; export type InternalAccountTypes = | InternalEthEoaAccount | InternalEthErc4337Account - | InternalBtcP2wpkhAccount; + | InternalBtcP2wpkhAccount + | InternalSolDataAccount; export const InternalAccountStruct = object({ ...KeyringAccountStruct.schema, diff --git a/packages/keyring-api/src/sol/index.ts b/packages/keyring-api/src/sol/index.ts new file mode 100644 index 00000000..fcb073fe --- /dev/null +++ b/packages/keyring-api/src/sol/index.ts @@ -0,0 +1 @@ +export * from './types'; diff --git a/packages/keyring-api/src/sol/types.test-d.ts b/packages/keyring-api/src/sol/types.test-d.ts new file mode 100644 index 00000000..8647f89e --- /dev/null +++ b/packages/keyring-api/src/sol/types.test-d.ts @@ -0,0 +1,7 @@ +import type { SolDataAccount } from './types'; +import type { KeyringAccount } from '../api'; +import type { Extends } from '../utils'; +import { expectTrue } from '../utils'; + +// `SolDataAccount` extends `KeyringAccount` +expectTrue>(); diff --git a/packages/keyring-api/src/sol/types.test.ts b/packages/keyring-api/src/sol/types.test.ts new file mode 100644 index 00000000..121f4eb3 --- /dev/null +++ b/packages/keyring-api/src/sol/types.test.ts @@ -0,0 +1,28 @@ +import { SolAddressStruct } from './types'; + +describe('types', () => { + describe('SolAddressStruct', () => { + it.each([ + '7EcDhSYGxXyscszYEp35KHN8vvw3svAuLKTzXwCFLtV', + 'A5R99q8qeyUhgwYAdp5h8pAD1iteVjCzpCv7G6JRZLaQ', + ])('is valid address: %s', (address) => { + expect(() => SolAddressStruct.assert(address)).not.toThrow(); + }); + + it.each([ + // Invalid lengths, too long (45 chars) + '7EcDhSYGxXyscszYEp35KHN8vvw3svAuLKTzXwCFLtV11', + // Too short (31 chars) + '7EcDhSYGxXyscszYEp35KHN8vvw', + // Empty or invalid input + '', + // Eth style address + '0x1234', + 'not-an-address', + ])('rejects invalid address: %s', (address) => { + expect(() => SolAddressStruct.assert(address)).toThrow( + `Expected a value of type \`SolAddress\`, but received: \`"${address}"\``, + ); + }); + }); +}); diff --git a/packages/keyring-api/src/sol/types.ts b/packages/keyring-api/src/sol/types.ts new file mode 100644 index 00000000..d01d9415 --- /dev/null +++ b/packages/keyring-api/src/sol/types.ts @@ -0,0 +1,44 @@ +import type { Infer } from '@metamask/superstruct'; +import { array, enums, literal } from '@metamask/superstruct'; + +import { KeyringAccountStruct, SolAccountType } from '../api'; +import { object, definePattern } from '../superstruct'; + +/** + * Solana addresses are represented in the format of a 256-bit ed25519 public key and + * are encoded using base58. + * They are usually 32 to 44 characters long. + */ +export const SolAddressStruct = definePattern( + 'SolAddress', + /^[1-9A-HJ-NP-Za-km-z]{32,44}$/iu, +); + +/** + * Supported Solana methods. + */ +export enum SolMethod { + // General transaction methods + SendAndConfirmTransaction = 'sendAndConfirmTransaction', +} + +export const SolDataAccountStruct = object({ + ...KeyringAccountStruct.schema, + + /** + * Account address. + */ + address: SolAddressStruct, + + /** + * Account type. + */ + type: literal(`${SolAccountType.DataAccount}`), + + /** + * Account supported methods. + */ + methods: array(enums([`${SolMethod.SendAndConfirmTransaction}`])), +}); + +export type SolDataAccount = Infer;