diff --git a/src/__tests__/cgw/accounts.spec.ts b/src/__tests__/cgw/accounts.spec.ts index 59a37cf..6ac218a 100644 --- a/src/__tests__/cgw/accounts.spec.ts +++ b/src/__tests__/cgw/accounts.spec.ts @@ -48,6 +48,7 @@ describe('CGW Auth tests', () => { const account = await cgw.getAccount(accessToken, created.address); expect(account.address).toBe(created.address); expect(account.id).toBe(created.id); + expect(account.name).toBe(created.name); } finally { await cgw.deleteAccount(accessToken, address); } @@ -119,6 +120,7 @@ describe('CGW Auth tests', () => { const account = await cgw.getAccount(accessToken, created.address); expect(account.address).toBe(created.address); expect(account.id).toBe(created.id); + expect(account.name).toBe(created.name); const dataTypes = await cgw.getDataTypes(); const upsertAccountDataSettingsDto = { accountDataSettings: dataTypes diff --git a/src/__tests__/cgw/address-books.spec.ts b/src/__tests__/cgw/address-books.spec.ts new file mode 100644 index 0000000..cebe53f --- /dev/null +++ b/src/__tests__/cgw/address-books.spec.ts @@ -0,0 +1,174 @@ +import { configuration } from '@/config/configuration'; +import { CGWAccount, ClientGatewayClient } from '@/datasources/cgw/cgw-client'; +import { faker } from '@faker-js/faker'; +import { ethers, getAddress, Wallet } from 'ethers'; + +let signer: Wallet; +let accessToken: string; + +const { privateKeys, walletAddresses } = configuration; + +const getAccessToken = async (cgw: ClientGatewayClient) => { + const { nonce } = await cgw.getNonce(); + const message = cgw.createSiweMessage(walletAddresses[0], nonce); + + const accessToken = await cgw.login({ + message, + signature: await signer.signMessage(message), + }); + + if (accessToken === undefined) { + throw new Error('Access token is undefined'); + } + + return accessToken; +}; + +describe('CGW Address Books tests', () => { + const cgw: ClientGatewayClient = new ClientGatewayClient(); + + beforeAll(async () => { + const { chain, rpc } = configuration; + const provider = new ethers.AlchemyProvider(chain.name, rpc.apiKey); + signer = new ethers.Wallet(privateKeys[0], provider); + accessToken = await getAccessToken(cgw); + }); + + let createdAccount: CGWAccount; + + describe('Address Books endpoints', () => { + it('should create an Account, and fail to get its Address Book', async () => { + const address = walletAddresses[0]; + const name = 'TestAccount'; + const chainId = configuration.chain.chainId; + const createAccountDto = { address, name }; + try { + createdAccount = await cgw.createAccount(accessToken, createAccountDto); + expect(createdAccount).toBeDefined(); + expect(createdAccount.address).toBe(address); + const account = await cgw.getAccount( + accessToken, + createdAccount.address, + ); + expect(account.address).toBe(createdAccount.address); + expect(account.id).toBe(createdAccount.id); + expect(account.name).toBe(createdAccount.name); + + // Enable setting for all data types + const dataTypes = await cgw.getDataTypes(); + const upsertAccountDataSettingsDto = { + accountDataSettings: dataTypes + .filter((dt) => dt.isActive) + .map((dt) => ({ + dataTypeId: dt.id, + enabled: true, + })), + }; + + await cgw.upsertAccountDataSettings( + accessToken, + address, + upsertAccountDataSettingsDto, + ); + + await cgw.getAddressBook(accessToken, address, chainId); + } catch (error) { + expect(error.response.data).toBe('Address Book not found'); + expect(error.response.status).toBe(404); + } finally { + await cgw.deleteAccount(accessToken, address); + } + }); + + it('should create an Account, create an AddressBookItem and retrieve the AddressBook', async () => { + const address = walletAddresses[0]; + const name = 'TestAccount'; + const chainId = configuration.chain.chainId; + const createAccountDto = { address, name }; + try { + createdAccount = await cgw.createAccount(accessToken, createAccountDto); + expect(createdAccount).toBeDefined(); + expect(createdAccount.address).toBe(address); + const account = await cgw.getAccount( + accessToken, + createdAccount.address, + ); + expect(account.address).toBe(createdAccount.address); + expect(account.id).toBe(createdAccount.id); + expect(account.name).toBe(createdAccount.name); + + // Enable setting for all data types + const dataTypes = await cgw.getDataTypes(); + const upsertAccountDataSettingsDto = { + accountDataSettings: dataTypes + .filter((dt) => dt.isActive) + .map((dt) => ({ + dataTypeId: dt.id, + enabled: true, + })), + }; + + await cgw.upsertAccountDataSettings( + accessToken, + address, + upsertAccountDataSettingsDto, + ); + + const createAddressBookItemDto = { + name: 'TestAddressBookItem', + address: faker.finance.ethereumAddress(), + }; + const secondCreateAddressBookItemDto = { + name: 'SecondTestAddressBookItem', + address: faker.finance.ethereumAddress(), + }; + const item = await cgw.createAddressBookItem( + accessToken, + address, + chainId, + createAddressBookItemDto, + ); + const secondItem = await cgw.createAddressBookItem( + accessToken, + address, + chainId, + secondCreateAddressBookItemDto, + ); + expect(item).toStrictEqual({ + id: expect.any(String), + name: createAddressBookItemDto.name, + address: getAddress(createAddressBookItemDto.address), // should be checksummed by the CGW + }); + expect(secondItem).toStrictEqual({ + id: expect.any(String), + name: secondCreateAddressBookItemDto.name, + address: getAddress(secondCreateAddressBookItemDto.address), // should be checksummed by the CGW + }); + const addressBook = await cgw.getAddressBook( + accessToken, + address, + chainId, + ); + expect(addressBook).toStrictEqual({ + id: expect.any(String), + accountId: account.id, + chainId, + data: [ + { + id: '1', + name: 'TestAddressBookItem', + address: getAddress(createAddressBookItemDto.address), // should be checksummed by the CGW + }, + { + id: '2', + name: 'SecondTestAddressBookItem', + address: getAddress(secondCreateAddressBookItemDto.address), // should be checksummed by the CGW + }, + ], + }); + } finally { + await cgw.deleteAccount(accessToken, address); + } + }); + }); +}); diff --git a/src/__tests__/cgw/counterfactual-safes.spec.ts b/src/__tests__/cgw/counterfactual-safes.spec.ts index 5246f5b..a5c7805 100644 --- a/src/__tests__/cgw/counterfactual-safes.spec.ts +++ b/src/__tests__/cgw/counterfactual-safes.spec.ts @@ -67,6 +67,7 @@ describe('CGW Counterfactual Safes tests', () => { ); expect(account.address).toBe(createdAccount.address); expect(account.id).toBe(createdAccount.id); + expect(account.name).toBe(createdAccount.name); // Enable setting for all data types const dataTypes = await cgw.getDataTypes(); diff --git a/src/datasources/cgw/cgw-client.ts b/src/datasources/cgw/cgw-client.ts index 6bd717e..70ed437 100644 --- a/src/datasources/cgw/cgw-client.ts +++ b/src/datasources/cgw/cgw-client.ts @@ -89,6 +89,7 @@ export interface CGWAccount { id: string; groupId: string | null; address: string; + name: string; } export interface CGWDataType { @@ -107,6 +108,16 @@ export interface CGWUpsertAccountDataSettingsDto { accountDataSettings: CGWAccountDataSetting[]; } +export interface CGWAddressBook { + accountId: string; + chainId: string; + data: Array<{ + id: string; + name: string; + address: string; + }>; +} + export interface CGWCounterfactualSafe { chainId: string; creator: string; @@ -118,6 +129,11 @@ export interface CGWCounterfactualSafe { threshold: number; } +export interface CGWCreateAddressBookItemDto { + name: string; + address: string; +} + export interface CGWCreateCounterfactualSafeDTO { chainId: string; fallbackHandler: string; @@ -257,6 +273,34 @@ export class ClientGatewayClient { } } + // Address Books endpoints + + async getAddressBook( + accessToken: string, + address: string, + chainId: string, + ): Promise { + const { data } = await httpClient.get( + `${this.baseUri}/v1/accounts/${address}/address-books/${chainId}`, + { headers: { Cookie: accessToken } }, + ); + return data; + } + + async createAddressBookItem( + accessToken: string, + address: string, + chainId: string, + createAddressBookItemDto: CGWCreateAddressBookItemDto, + ): Promise { + const { data } = await httpClient.post( + `${this.baseUri}/v1/accounts/${address}/address-books/${chainId}`, + createAddressBookItemDto, + { headers: { Cookie: accessToken } }, + ); + return data; + } + // Counterfactual Safes endpoints async createCounterfactualSafe(