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

Feat extended safe info #580

Merged
merged 4 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
5 changes: 5 additions & 0 deletions .changeset/dirty-meals-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@safe-global/safe-apps-sdk': minor
---

safe.getInfo() endpoint should return extended safe information similar to api-kit's getSafeInfo endpoint
6 changes: 3 additions & 3 deletions packages/safe-apps-sdk/src/safe/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Methods } from '../communication/methods.js';
import { RPC_CALLS } from '../eth/constants.js';
import {
Communicator,
SafeInfo,
ChainInfo,
SafeBalances,
GetBalanceParams,
Expand All @@ -14,6 +13,7 @@ import {
AddressBookItem,
isObjectEIP712TypedData,
EIP712TypedData,
SafeInfoExtended,
} from '../types/index.js';
import requirePermission from '../decorators/requirePermissions.js';

Expand All @@ -33,8 +33,8 @@ class Safe {
return response.data;
}

async getInfo(): Promise<SafeInfo> {
const response = await this.communicator.send<Methods.getSafeInfo, undefined, SafeInfo>(
async getInfo(): Promise<SafeInfoExtended> {
const response = await this.communicator.send<Methods.getSafeInfo, undefined, SafeInfoExtended>(
Methods.getSafeInfo,
undefined,
);
Expand Down
148 changes: 27 additions & 121 deletions packages/safe-apps-sdk/src/safe/safe.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
import SDK from '../sdk.js';
import { SafeInfo } from '../types/index.js';
import { SafeInfoExtended } from '../types/index.js';
import { Methods } from '../communication/methods.js';
import { PostMessageOptions } from '../types/index.js';
import { PermissionsError } from '../types/permissions.js';
import { Wallet } from '../wallet/index.js';

const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

export const ExtendedSafeInfo: SafeInfoExtended = {
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
nonce: 1,
implementation: '0x3E5c63644E683549055b9Be8653de26E0B4CD36E',
modules: null,
fallbackHandler: null,
version: '1.3.0',
guard: null,
owners: [],
threshold: 1,
isReadOnly: false,
};

describe('Safe Apps SDK safe methods', () => {
const sdkInstance = new SDK();
let postMessageSpy: jest.SpyInstance<void, [message: any, options?: PostMessageOptions]>;

Check warning on line 26 in packages/safe-apps-sdk/src/safe/safe.test.ts

View workflow job for this annotation

GitHub Actions / eslint

Unexpected any. Specify a different type

beforeEach(() => {
postMessageSpy = jest.spyOn(window.parent, 'postMessage');
Expand All @@ -33,16 +47,7 @@
describe('SDK.safe.calculateMessageHash', () => {
test('Should generate correct EIP-191 message hash', () => {
const safeInfoSpy = jest.spyOn(sdkInstance.safe, 'getInfo');
safeInfoSpy.mockImplementationOnce(
(): Promise<SafeInfo> =>
Promise.resolve({
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
owners: [],
threshold: 1,
isReadOnly: false,
}),
);
safeInfoSpy.mockImplementationOnce((): Promise<SafeInfoExtended> => Promise.resolve(ExtendedSafeInfo));
// to test message/hash I signed a test message on rinkeby
// https://dashboard.tenderly.co/tx/rinkeby/0x9308fb61d9f4282080334e3f35b357fc689e06808b8ad2817536813948e3720d
const message = 'approve rugpull';
Expand All @@ -56,16 +61,7 @@
describe('SDK.safe.calculateTypedMessageHash', () => {
test('Should generate correct EIP-712 message hash', () => {
const safeInfoSpy = jest.spyOn(sdkInstance.safe, 'getInfo');
safeInfoSpy.mockImplementationOnce(
(): Promise<SafeInfo> =>
Promise.resolve({
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
owners: [],
threshold: 1,
isReadOnly: false,
}),
);
safeInfoSpy.mockImplementationOnce((): Promise<SafeInfoExtended> => Promise.resolve(ExtendedSafeInfo));

const typedMessage = {
domain: {
Expand Down Expand Up @@ -107,16 +103,7 @@
describe('SDK.safe.check1271Signature', () => {
test('Should send a valid message to the interface', async () => {
const safeInfoSpy = jest.spyOn(sdkInstance.safe, 'getInfo');
safeInfoSpy.mockImplementationOnce(
(): Promise<SafeInfo> =>
Promise.resolve({
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
owners: [],
threshold: 1,
isReadOnly: false,
}),
);
safeInfoSpy.mockImplementationOnce((): Promise<SafeInfoExtended> => Promise.resolve(ExtendedSafeInfo));
const message = '0x617070726f76652072756770756c6c0000000000000000000000000000000000'; // stringToHex('approve rugpull', { size: 32 })
// @ts-expect-error method is private but we are testing it
sdkInstance.safe.check1271Signature(message);
Expand All @@ -143,16 +130,7 @@
const safeInfoSpy = jest.spyOn(sdkInstance.safe, 'getInfo');
// @ts-expect-error method is private but we are testing it
const rpcCallSpy = jest.spyOn(sdkInstance.safe.communicator, 'send');
safeInfoSpy.mockImplementationOnce(
(): Promise<SafeInfo> =>
Promise.resolve({
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
owners: [],
threshold: 1,
isReadOnly: false,
}),
);
safeInfoSpy.mockImplementationOnce((): Promise<SafeInfoExtended> => Promise.resolve(ExtendedSafeInfo));
rpcCallSpy.mockImplementationOnce(() =>
Promise.resolve({
id: '1',
Expand All @@ -170,16 +148,7 @@
const safeInfoSpy = jest.spyOn(sdkInstance.safe, 'getInfo');
// @ts-expect-error method is private but we are testing it
const rpcCallSpy = jest.spyOn(sdkInstance.safe.communicator, 'send');
safeInfoSpy.mockImplementationOnce(
(): Promise<SafeInfo> =>
Promise.resolve({
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
owners: [],
threshold: 1,
isReadOnly: false,
}),
);
safeInfoSpy.mockImplementationOnce((): Promise<SafeInfoExtended> => Promise.resolve(ExtendedSafeInfo));
rpcCallSpy.mockImplementationOnce(() => Promise.reject(new Error('Hash not approved')));

const message = '0x68616c6c6f000000000000000000000000000000000000000000000000000000'; // stringToHex('hallo')
Expand All @@ -191,16 +160,7 @@
describe('SDK.safe.check1271SignatureBytes', () => {
test('Should send a valid message to the interface', async () => {
const safeInfoSpy = jest.spyOn(sdkInstance.safe, 'getInfo');
safeInfoSpy.mockImplementationOnce(
(): Promise<SafeInfo> =>
Promise.resolve({
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
owners: [],
threshold: 1,
isReadOnly: false,
}),
);
safeInfoSpy.mockImplementationOnce((): Promise<SafeInfoExtended> => Promise.resolve(ExtendedSafeInfo));
const message = '0x617070726f76652072756770756c6c0000000000000000000000000000000000'; // stringToHex('approve rugpull', { size: 32 })
// @ts-expect-error method is private but we are testing it
sdkInstance.safe.check1271SignatureBytes(message);
Expand All @@ -227,16 +187,7 @@
const safeInfoSpy = jest.spyOn(sdkInstance.safe, 'getInfo');
// @ts-expect-error method is private but we are testing it
const rpcCallSpy = jest.spyOn(sdkInstance.safe.communicator, 'send');
safeInfoSpy.mockImplementationOnce(
(): Promise<SafeInfo> =>
Promise.resolve({
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
owners: [],
threshold: 1,
isReadOnly: false,
}),
);
safeInfoSpy.mockImplementationOnce((): Promise<SafeInfoExtended> => Promise.resolve(ExtendedSafeInfo));
rpcCallSpy.mockImplementationOnce(() =>
Promise.resolve({
id: '1',
Expand All @@ -254,16 +205,7 @@
const safeInfoSpy = jest.spyOn(sdkInstance.safe, 'getInfo');
// @ts-expect-error method is private but we are testing it
const rpcCallSpy = jest.spyOn(sdkInstance.safe.communicator, 'send');
safeInfoSpy.mockImplementationOnce(
(): Promise<SafeInfo> =>
Promise.resolve({
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
owners: [],
threshold: 1,
isReadOnly: false,
}),
);
safeInfoSpy.mockImplementationOnce((): Promise<SafeInfoExtended> => Promise.resolve(ExtendedSafeInfo));
rpcCallSpy.mockImplementationOnce(() => Promise.reject(new Error('Hash not approved')));

const message = '0x68616c6c6f000000000000000000000000000000000000000000000000000000'; // stringToHex('hallo')
Expand All @@ -288,16 +230,7 @@
describe('SDK.safe.isMessageHashSigned', () => {
test('Should send call messages to check the message the interface', async () => {
const safeInfoSpy = jest.spyOn(sdkInstance.safe, 'getInfo');
safeInfoSpy.mockImplementation(
(): Promise<SafeInfo> =>
Promise.resolve({
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
owners: [],
threshold: 1,
isReadOnly: false,
}),
);
safeInfoSpy.mockImplementationOnce((): Promise<SafeInfoExtended> => Promise.resolve(ExtendedSafeInfo));

const message = '0x617070726f76652072756770756c6c0000000000000000000000000000000000'; // stringToHex('approve rugpull', { size: 32 })

Expand Down Expand Up @@ -326,16 +259,7 @@
const safeInfoSpy = jest.spyOn(sdkInstance.safe, 'getInfo');
// @ts-expect-error method is private but we are testing it
const check1271SignatureSpy = jest.spyOn(sdkInstance.safe, 'check1271Signature');
safeInfoSpy.mockImplementationOnce(
(): Promise<SafeInfo> =>
Promise.resolve({
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
owners: [],
threshold: 1,
isReadOnly: false,
}),
);
safeInfoSpy.mockImplementationOnce((): Promise<SafeInfoExtended> => Promise.resolve(ExtendedSafeInfo));
// @ts-expect-error ts fails to infer the return type because of a private method
check1271SignatureSpy.mockImplementationOnce(() => Promise.resolve(true));

Expand All @@ -354,16 +278,7 @@
const check1271SignatureSpy = jest.spyOn(sdkInstance.safe, 'check1271Signature');
// @ts-expect-error method is private but we are testing it
const check1271SignatureBytesSpy = jest.spyOn(sdkInstance.safe, 'check1271SignatureBytes');
safeInfoSpy.mockImplementationOnce(
(): Promise<SafeInfo> =>
Promise.resolve({
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
owners: [],
threshold: 1,
isReadOnly: false,
}),
);
safeInfoSpy.mockImplementationOnce((): Promise<SafeInfoExtended> => Promise.resolve(ExtendedSafeInfo));
// @ts-expect-error ts fails to infer the return type because of a private method
check1271SignatureSpy.mockImplementationOnce(() => Promise.resolve(false));
// @ts-expect-error ts fails to infer the return type because of a private method
Expand All @@ -384,16 +299,7 @@
const check1271SignatureSpy = jest.spyOn(sdkInstance.safe, 'check1271Signature');
// @ts-expect-error method is private but we are testing it
const check1271SignatureBytesSpy = jest.spyOn(sdkInstance.safe, 'check1271SignatureBytes');
safeInfoSpy.mockImplementationOnce(
(): Promise<SafeInfo> =>
Promise.resolve({
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
owners: [],
threshold: 1,
isReadOnly: false,
}),
);
safeInfoSpy.mockImplementationOnce((): Promise<SafeInfoExtended> => Promise.resolve(ExtendedSafeInfo));
// @ts-expect-error ts fails to infer the return type because of a private method
check1271SignatureSpy.mockImplementationOnce(() => Promise.resolve(false));
// @ts-expect-error ts fails to infer the return type because of a private method
Expand Down
14 changes: 3 additions & 11 deletions packages/safe-apps-sdk/src/txs/txs.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import SDK, { SafeInfo } from '../index.js';
import SDK, { SafeInfoExtended } from '../index.js';
import { Methods } from '../communication/methods.js';
import { PostMessageOptions } from '../types/index.js';
import { ExtendedSafeInfo } from '../safe/safe.test';

describe('Safe Apps SDK transaction methods', () => {
const sdkInstance = new SDK();
let spy: jest.SpyInstance<void, [message: any, options?: PostMessageOptions]>;

Check warning on line 8 in packages/safe-apps-sdk/src/txs/txs.test.ts

View workflow job for this annotation

GitHub Actions / eslint

Unexpected any. Specify a different type

beforeEach(() => {
spy = jest.spyOn(window.parent, 'postMessage');
Expand Down Expand Up @@ -70,16 +71,7 @@

test('Should include params with non-hashed message to the typed message body', async () => {
const safeInfoSpy = jest.spyOn(sdkInstance.safe, 'getInfo');
safeInfoSpy.mockImplementationOnce(
(): Promise<SafeInfo> =>
Promise.resolve({
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
owners: [],
threshold: 1,
isReadOnly: false,
}),
);
safeInfoSpy.mockImplementationOnce((): Promise<SafeInfoExtended> => Promise.resolve(ExtendedSafeInfo));

const domain = {
name: 'Ether Mail',
Expand Down
9 changes: 9 additions & 0 deletions packages/safe-apps-sdk/src/types/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
export type EIP712TypedData = {
domain: TypedDataDomain;
types: TypedMessageTypes;
message: Record<string, any>;

Check warning on line 53 in packages/safe-apps-sdk/src/types/sdk.ts

View workflow job for this annotation

GitHub Actions / eslint

Unexpected any. Specify a different type
primaryType?: string;
};

Expand All @@ -76,12 +76,21 @@
isReadOnly: boolean;
};

export type SafeInfoExtended = SafeInfo & {
nonce: number;
implementation: string;
modules: string[] | null;
fallbackHandler: string | null;
guard: string | null;
version: string | null;
};

export type EnvironmentInfo = {
origin: string;
};

export type PostMessageOptions = {
transfer?: any[];

Check warning on line 93 in packages/safe-apps-sdk/src/types/sdk.ts

View workflow job for this annotation

GitHub Actions / eslint

Unexpected any. Specify a different type
};

export type AddressBookItem = {
Expand Down
5 changes: 0 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4464,11 +4464,6 @@ big.js@^5.2.2:
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==

bignumber.js@^9.0.2:
version "9.1.0"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.0.tgz#8d340146107fe3a6cb8d40699643c302e8773b62"
integrity sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==

binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
Expand Down
Loading