Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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/blue-maps-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@safe-global/safe-apps-provider': patch
---

Update EIP-5792 according to spec.
6 changes: 6 additions & 0 deletions packages/safe-apps-provider/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @safe-global/safe-apps-provider

## 0.18.5

### Patch Changes

- 5219a05: Update EIP-5792 implementation according to spec. and fix pending status

## 0.18.4

### Patch Changes
Expand Down
52 changes: 27 additions & 25 deletions packages/safe-apps-provider/dist/provider.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/safe-apps-provider/dist/provider.js.map

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions packages/safe-apps-provider/dist/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,45 @@ export interface EIP1193Provider extends EventEmitter {
disconnect(): Promise<void>;
request(args: RequestArguments): Promise<unknown>;
}
type Capability = {
[key: string]: unknown;
optional?: boolean;
};
export type SendCallsParams = {
version: string;
id?: string;
from?: `0x${string}`;
chainId: `0x${string}`;
calls: Array<{
to?: `0x${string}`;
data?: `0x${string}`;
value?: `0x${string}`;
capabilities?: Record<string, Capability>;
}>;
capabilities?: Record<string, Capability>;
};
export type SendCallsResult = {
id: string;
capabilities?: Record<string, unknown>;
};
export type GetCallsParams = `0x${string}`;
export type GetCallsResult = {
version: '1.0';
id: `0x${string}`;
chainId: `0x${string}`;
status: 100 | 200 | 400 | 500 | 600;
receipts?: Array<{
logs: Array<{
address: `0x${string}`;
data: `0x${string}`;
topics: Array<`0x${string}`>;
}>;
status: `0x${string}`;
blockHash: `0x${string}`;
blockNumber: `0x${string}`;
gasUsed: `0x${string}`;
transactionHash: `0x${string}`;
}>;
capabilities?: Record<string, unknown>;
};
export {};
2 changes: 1 addition & 1 deletion packages/safe-apps-provider/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@safe-global/safe-apps-provider",
"version": "0.18.4",
"version": "0.18.5",
"description": "A provider wrapper of Safe Apps SDK",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
Expand Down
84 changes: 43 additions & 41 deletions packages/safe-apps-provider/src/provider.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import SafeAppsSDK, { SafeInfo, TransactionStatus, Web3TransactionObject } from '@safe-global/safe-apps-sdk';
import { EventEmitter } from 'events';
import { EIP1193Provider } from './types';
import { EIP1193Provider, GetCallsParams, GetCallsResult, SendCallsParams, SendCallsResult } from './types';
import { getLowerCase, numberToHex } from './utils';

// The API is based on Ethereum JavaScript API Provider Standard. Link: https://eips.ethereum.org/EIPS/eip-1193
Expand Down Expand Up @@ -196,61 +196,67 @@
return this.sdk.eth.setSafeSettings([params[0]]);

case 'wallet_sendCalls': {
if (params[0].from !== this.safe.safeAddress) {
const { from, calls, chainId }: SendCallsParams = params[0];

if (chainId !== numberToHex(this.chainId)) {
throw new Error(`Safe is not on chain ${chainId}`);
}

if (from !== this.safe.safeAddress) {
throw Error('Invalid from address');
}

const txs = params[0].calls.map(
(
call: { to?: `0x${string}`; data?: `0x${string}`; value?: `0x${string}`; chainId?: `0x${string}` },
i: number,
) => {
if (call.chainId !== numberToHex(this.chainId)) {
throw new Error(`Invalid call #${i}: Safe is not on chain ${call.chainId}`);
}
if (!call.to) {
throw new Error(`Invalid call #${i}: missing "to" field`);
}
return {
to: call.to,
data: call.data ?? '0x',
value: call.value ?? numberToHex(0),
};
},
);
const txs = calls.map((call, i) => {
if (!call.to) {
throw new Error(`Invalid call #${i}: missing "to" field`);
}
return {
to: call.to,
data: call.data ?? '0x',
value: call.value ?? numberToHex(0),
};
});

const { safeTxHash } = await this.sdk.txs.send({ txs });
return safeTxHash;

const result: SendCallsResult = {
id: safeTxHash,
};

return result;
}

case 'wallet_getCallsStatus': {
const safeTxHash: GetCallsParams = params[0];

const CallStatus: {
[key in TransactionStatus]: 'PENDING' | 'CONFIRMED';
[key in TransactionStatus]: GetCallsResult['status'];
} = {
[TransactionStatus.AWAITING_CONFIRMATIONS]: 'PENDING',
[TransactionStatus.AWAITING_EXECUTION]: 'PENDING',
[TransactionStatus.CANCELLED]: 'CONFIRMED',
[TransactionStatus.FAILED]: 'CONFIRMED',
[TransactionStatus.SUCCESS]: 'CONFIRMED',
[TransactionStatus.AWAITING_CONFIRMATIONS]: 100,
[TransactionStatus.AWAITING_EXECUTION]: 100,
[TransactionStatus.SUCCESS]: 200,
[TransactionStatus.CANCELLED]: 400,
[TransactionStatus.FAILED]: 500,
};

const tx = await this.sdk.txs.getBySafeTxHash(params[0]);
const tx = await this.sdk.txs.getBySafeTxHash(safeTxHash);

const status = CallStatus[tx.txStatus];
const result: GetCallsResult = {
version: '1.0',
id: safeTxHash,
chainId: numberToHex(this.chainId),
status: CallStatus[tx.txStatus],
};

// Transaction is queued
if (!tx.txHash) {
return {
status,
};
return result;
}

// If transaction is executing, receipt is null
const receipt = await this.sdk.eth.getTransactionReceipt([tx.txHash]);
if (!receipt) {
return {
status,
};
return result;
}

const calls =
Expand All @@ -263,20 +269,16 @@
const blockNumber = Number(receipt.blockNumber);
const gasUsed = Number(receipt.gasUsed);

const receipts = Array(calls).fill({
result.receipts = Array(calls).fill({
logs: receipt.logs,
status: numberToHex(tx.txStatus === TransactionStatus.SUCCESS ? 1 : 0),
chainId: numberToHex(this.chainId),
blockHash: receipt.blockHash,
blockNumber: numberToHex(blockNumber),
gasUsed: numberToHex(gasUsed),
transactionHash: tx.txHash,
});

return {
status,
receipts,
};
return result;
}

case 'wallet_showCallsStatus': {
Expand All @@ -301,7 +303,7 @@

// this method is needed for ethers v4
// https://github.com/ethers-io/ethers.js/blob/427e16826eb15d52d25c4f01027f8db22b74b76c/src.ts/providers/web3-provider.ts#L41-L55
send(request: any, callback: (error: any, response?: any) => void): void {

Check warning on line 306 in packages/safe-apps-provider/src/provider.ts

View workflow job for this annotation

GitHub Actions / eslint

Unexpected any. Specify a different type

Check warning on line 306 in packages/safe-apps-provider/src/provider.ts

View workflow job for this annotation

GitHub Actions / eslint

Unexpected any. Specify a different type

Check warning on line 306 in packages/safe-apps-provider/src/provider.ts

View workflow job for this annotation

GitHub Actions / eslint

Unexpected any. Specify a different type
if (!request) callback('Undefined request');
this.request(request)
.then((result) => callback(null, { jsonrpc: '2.0', id: request.id, result }))
Expand Down
57 changes: 57 additions & 0 deletions packages/safe-apps-provider/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,64 @@
export type ProviderAccounts = string[];

export interface EIP1193Provider extends EventEmitter {
connect(params?: any): Promise<void>;

Check warning on line 28 in packages/safe-apps-provider/src/types.ts

View workflow job for this annotation

GitHub Actions / eslint

Unexpected any. Specify a different type
disconnect(): Promise<void>;
request(args: RequestArguments): Promise<unknown>;
}

// EIP-5792 - https://eips.ethereum.org/EIPS/eip-5792

// wallet_sendCalls

type Capability = {
[key: string]: unknown;
optional?: boolean;
};

export type SendCallsParams = {
version: string;
id?: string;
from?: `0x${string}`;
chainId: `0x${string}`;
calls: Array<{
to?: `0x${string}`;
data?: `0x${string}`;
value?: `0x${string}`;
capabilities?: Record<string, Capability>;
}>;
capabilities?: Record<string, Capability>;
};

export type SendCallsResult = {
id: string;
capabilities?: Record<string, unknown>;
};

// wallet_getCallsStatus

export type GetCallsParams = `0x${string}`;

export type GetCallsResult = {
version: '1.0';
id: `0x${string}`;
chainId: `0x${string}`;
status:
| 100 // Batch has been received by the wallet but has not completed execution onchain (pending)
| 200 // Batch has been included onchain without reverts, receipts array contains info of all calls (confirmed)
| 400 // Batch has not been included onchain and wallet will not retry (offchain failure)
| 500 // Batch reverted completely and only changes related to gas charge may have been included onchain (chain rules failure)
| 600; // Batch reverted partially and some changes related to batch calls may have been included onchain (partial chain rules failure);
receipts?: Array<{
logs: Array<{
address: `0x${string}`;
data: `0x${string}`;
topics: Array<`0x${string}`>;
}>;
status: `0x${string}`; // Hex 1 or 0 for success or failure, respectively
blockHash: `0x${string}`;
blockNumber: `0x${string}`;
gasUsed: `0x${string}`;
transactionHash: `0x${string}`;
}>;
capabilities?: Record<string, unknown>;
};
Loading