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 8e43406 commit 31dd7fd
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 110 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 @@ -13,7 +13,7 @@ import {
EnvironmentInfo,
AddressBookItem,
isObjectEIP712TypedData,
EIP712TypedData,
EIP712TypedData, SafeInfoExtended,

Check failure on line 16 in packages/safe-apps-sdk/src/safe/index.ts

View workflow job for this annotation

GitHub Actions / eslint

Insert `⏎·`
} 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
138 changes: 40 additions & 98 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 { SafeInfo, SafeInfoExtended } from '../types/index.js';

Check failure on line 2 in packages/safe-apps-sdk/src/safe/safe.test.ts

View workflow job for this annotation

GitHub Actions / eslint

'SafeInfo' is defined but never used
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
Expand Down Expand Up @@ -34,14 +48,8 @@ describe('Safe Apps SDK safe methods', () => {
test('Should generate correct EIP-191 message hash', () => {
const safeInfoSpy = jest.spyOn(sdkInstance.safe, 'getInfo');
safeInfoSpy.mockImplementationOnce(

Check failure on line 50 in packages/safe-apps-sdk/src/safe/safe.test.ts

View workflow job for this annotation

GitHub Actions / eslint

Replace `⏎········():·Promise<SafeInfoExtended>·=>⏎··········Promise.resolve(ExtendedSafeInfo),⏎······` with `():·Promise<SafeInfoExtended>·=>·Promise.resolve(ExtendedSafeInfo)`
(): Promise<SafeInfo> =>
Promise.resolve({
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
owners: [],
threshold: 1,
isReadOnly: false,
}),
(): Promise<SafeInfoExtended> =>
Promise.resolve(ExtendedSafeInfo),
);
// to test message/hash I signed a test message on rinkeby
// https://dashboard.tenderly.co/tx/rinkeby/0x9308fb61d9f4282080334e3f35b357fc689e06808b8ad2817536813948e3720d
Expand All @@ -57,14 +65,8 @@ describe('Safe Apps SDK safe methods', () => {
test('Should generate correct EIP-712 message hash', () => {
const safeInfoSpy = jest.spyOn(sdkInstance.safe, 'getInfo');
safeInfoSpy.mockImplementationOnce(

Check failure on line 67 in packages/safe-apps-sdk/src/safe/safe.test.ts

View workflow job for this annotation

GitHub Actions / eslint

Replace `⏎········():·Promise<SafeInfoExtended>·=>⏎··········Promise.resolve(ExtendedSafeInfo),⏎······` with `():·Promise<SafeInfoExtended>·=>·Promise.resolve(ExtendedSafeInfo)`
(): Promise<SafeInfo> =>
Promise.resolve({
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
owners: [],
threshold: 1,
isReadOnly: false,
}),
(): Promise<SafeInfoExtended> =>
Promise.resolve(ExtendedSafeInfo),
);

const typedMessage = {
Expand Down Expand Up @@ -108,14 +110,8 @@ describe('Safe Apps SDK safe methods', () => {
test('Should send a valid message to the interface', async () => {
const safeInfoSpy = jest.spyOn(sdkInstance.safe, 'getInfo');
safeInfoSpy.mockImplementationOnce(

Check failure on line 112 in packages/safe-apps-sdk/src/safe/safe.test.ts

View workflow job for this annotation

GitHub Actions / eslint

Replace `⏎········():·Promise<SafeInfoExtended>·=>⏎··········Promise.resolve(ExtendedSafeInfo),⏎······` with `():·Promise<SafeInfoExtended>·=>·Promise.resolve(ExtendedSafeInfo)`
(): Promise<SafeInfo> =>
Promise.resolve({
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
owners: [],
threshold: 1,
isReadOnly: false,
}),
(): Promise<SafeInfoExtended> =>
Promise.resolve(ExtendedSafeInfo),
);
const message = '0x617070726f76652072756770756c6c0000000000000000000000000000000000'; // stringToHex('approve rugpull', { size: 32 })
// @ts-expect-error method is private but we are testing it
Expand Down Expand Up @@ -144,14 +140,8 @@ describe('Safe Apps SDK safe methods', () => {
// @ts-expect-error method is private but we are testing it
const rpcCallSpy = jest.spyOn(sdkInstance.safe.communicator, 'send');
safeInfoSpy.mockImplementationOnce(

Check failure on line 142 in packages/safe-apps-sdk/src/safe/safe.test.ts

View workflow job for this annotation

GitHub Actions / eslint

Replace `⏎········():·Promise<SafeInfoExtended>·=>⏎··········Promise.resolve(ExtendedSafeInfo),⏎······` with `():·Promise<SafeInfoExtended>·=>·Promise.resolve(ExtendedSafeInfo)`
(): Promise<SafeInfo> =>
Promise.resolve({
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
owners: [],
threshold: 1,
isReadOnly: false,
}),
(): Promise<SafeInfoExtended> =>
Promise.resolve(ExtendedSafeInfo),
);
rpcCallSpy.mockImplementationOnce(() =>
Promise.resolve({
Expand All @@ -171,14 +161,8 @@ describe('Safe Apps SDK safe methods', () => {
// @ts-expect-error method is private but we are testing it
const rpcCallSpy = jest.spyOn(sdkInstance.safe.communicator, 'send');
safeInfoSpy.mockImplementationOnce(

Check failure on line 163 in packages/safe-apps-sdk/src/safe/safe.test.ts

View workflow job for this annotation

GitHub Actions / eslint

Replace `⏎········():·Promise<SafeInfoExtended>·=>⏎··········Promise.resolve(ExtendedSafeInfo),⏎······` with `():·Promise<SafeInfoExtended>·=>·Promise.resolve(ExtendedSafeInfo)`
(): Promise<SafeInfo> =>
Promise.resolve({
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
owners: [],
threshold: 1,
isReadOnly: false,
}),
(): Promise<SafeInfoExtended> =>
Promise.resolve(ExtendedSafeInfo),
);
rpcCallSpy.mockImplementationOnce(() => Promise.reject(new Error('Hash not approved')));

Expand All @@ -192,14 +176,8 @@ describe('Safe Apps SDK safe methods', () => {
test('Should send a valid message to the interface', async () => {
const safeInfoSpy = jest.spyOn(sdkInstance.safe, 'getInfo');
safeInfoSpy.mockImplementationOnce(

Check failure on line 178 in packages/safe-apps-sdk/src/safe/safe.test.ts

View workflow job for this annotation

GitHub Actions / eslint

Replace `⏎········():·Promise<SafeInfoExtended>·=>⏎··········Promise.resolve(ExtendedSafeInfo),⏎······` with `():·Promise<SafeInfoExtended>·=>·Promise.resolve(ExtendedSafeInfo)`
(): Promise<SafeInfo> =>
Promise.resolve({
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
owners: [],
threshold: 1,
isReadOnly: false,
}),
(): Promise<SafeInfoExtended> =>
Promise.resolve(ExtendedSafeInfo),
);
const message = '0x617070726f76652072756770756c6c0000000000000000000000000000000000'; // stringToHex('approve rugpull', { size: 32 })
// @ts-expect-error method is private but we are testing it
Expand Down Expand Up @@ -228,14 +206,8 @@ describe('Safe Apps SDK safe methods', () => {
// @ts-expect-error method is private but we are testing it
const rpcCallSpy = jest.spyOn(sdkInstance.safe.communicator, 'send');
safeInfoSpy.mockImplementationOnce(

Check failure on line 208 in packages/safe-apps-sdk/src/safe/safe.test.ts

View workflow job for this annotation

GitHub Actions / eslint

Replace `⏎········():·Promise<SafeInfoExtended>·=>⏎··········Promise.resolve(ExtendedSafeInfo),⏎······` with `():·Promise<SafeInfoExtended>·=>·Promise.resolve(ExtendedSafeInfo)`
(): Promise<SafeInfo> =>
Promise.resolve({
chainId: 4,
safeAddress: '0x9C6FEA0B2eAc5b6D8bBB6C30401D42aA95398190',
owners: [],
threshold: 1,
isReadOnly: false,
}),
(): Promise<SafeInfoExtended> =>
Promise.resolve(ExtendedSafeInfo),
);
rpcCallSpy.mockImplementationOnce(() =>
Promise.resolve({
Expand All @@ -255,14 +227,8 @@ describe('Safe Apps SDK safe methods', () => {
// @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,
}),
(): Promise<SafeInfoExtended> =>
Promise.resolve(ExtendedSafeInfo),
);
rpcCallSpy.mockImplementationOnce(() => Promise.reject(new Error('Hash not approved')));

Expand All @@ -288,15 +254,9 @@ 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 @@ -327,14 +287,8 @@ describe('Safe Apps SDK safe methods', () => {
// @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,
}),
(): 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 @@ -355,14 +309,8 @@ describe('Safe Apps SDK safe methods', () => {
// @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,
}),
(): 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));
Expand All @@ -385,14 +333,8 @@ describe('Safe Apps SDK safe methods', () => {
// @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,
}),
(): 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));
Expand Down
13 changes: 4 additions & 9 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 @@ -71,14 +72,8 @@ 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,
}),
(): Promise<SafeInfoExtended> =>
Promise.resolve(ExtendedSafeInfo),
);

const domain = {
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 31dd7fd

Please sign in to comment.