Skip to content

Commit

Permalink
feat: getInfo endpoint returns extended Safe Info
Browse files Browse the repository at this point in the history
api-kit’s getSafeInfo endpoint returns way more information than our
communicator’s getInfo endpoint. This causes some dapps to use the
getInfo endpoint for just the readOnly flag (when running in an iframe)
and then to do api-kit getSafeInfo for the nonce and other props.
By returning nearly the same information from the getInfo endpoint we
make developers live easier.

.
  • Loading branch information
compojoom committed Jun 12, 2024
1 parent 6738a87 commit ee5cb36
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 135 deletions.
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,12 +1,26 @@
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]>;
Expand All @@ -33,16 +47,7 @@ describe('Safe Apps SDK safe methods', () => {
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('Safe Apps SDK safe methods', () => {
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('Safe Apps SDK safe methods', () => {
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 @@ describe('Safe Apps SDK safe methods', () => {
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 @@ describe('Safe Apps SDK safe methods', () => {
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('Safe Apps SDK safe methods', () => {
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 @@ describe('Safe Apps SDK safe methods', () => {
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 @@ describe('Safe Apps SDK safe methods', () => {
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('Safe Apps SDK safe methods', () => {
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 @@ describe('Safe Apps SDK safe methods', () => {
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 @@ describe('Safe Apps SDK safe methods', () => {
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 @@ describe('Safe Apps SDK safe methods', () => {
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,6 +1,7 @@
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();
Expand Down Expand Up @@ -70,16 +71,7 @@ describe('Safe Apps SDK transaction methods', () => {

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 @@ -76,6 +76,15 @@ export type SafeInfo = {
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;
};
Expand Down

0 comments on commit ee5cb36

Please sign in to comment.