@rabby-wallet/gnosis-sdk 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +54 -0
- package/dist/api.js +39 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +3 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +163 -0
- package/dist/type.d.ts +19 -0
- package/dist/type.js +1 -0
- package/dist/utils.d.ts +12 -0
- package/dist/utils.js +156 -0
- package/package.json +3 -2
- package/src/api.ts +1 -1
- package/src/index.ts +16 -12
- package/src/utils.ts +0 -19
- package/tsconfig.json +2 -1
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Axios } from "axios";
|
|
2
|
+
export interface SafeInfo {
|
|
3
|
+
address: string;
|
|
4
|
+
fallbackHandler: string;
|
|
5
|
+
guard: string;
|
|
6
|
+
masterCopy: string;
|
|
7
|
+
modules: string[];
|
|
8
|
+
nonce: number;
|
|
9
|
+
owners: string[];
|
|
10
|
+
threshold: number;
|
|
11
|
+
version: string;
|
|
12
|
+
}
|
|
13
|
+
interface ConfirmationItem {
|
|
14
|
+
owner: string;
|
|
15
|
+
submissionDate: string;
|
|
16
|
+
transactionHash: string | null;
|
|
17
|
+
signature: string;
|
|
18
|
+
signatureType: string;
|
|
19
|
+
}
|
|
20
|
+
export interface SafeTransactionItem {
|
|
21
|
+
safe: string;
|
|
22
|
+
to: string;
|
|
23
|
+
value: string;
|
|
24
|
+
data: string | null;
|
|
25
|
+
operation: number;
|
|
26
|
+
gasToken: string;
|
|
27
|
+
safeTxGas: number;
|
|
28
|
+
baseGas: number;
|
|
29
|
+
gasPrice: string;
|
|
30
|
+
refundReceiver: string;
|
|
31
|
+
nonce: number;
|
|
32
|
+
executionDate: string | null;
|
|
33
|
+
submissionDate: string;
|
|
34
|
+
modified: string;
|
|
35
|
+
blockNumber: number | null;
|
|
36
|
+
transactionHash: string | null;
|
|
37
|
+
safeTxHash: string;
|
|
38
|
+
executor: string | null;
|
|
39
|
+
isExecuted: boolean;
|
|
40
|
+
confirmations: ConfirmationItem[];
|
|
41
|
+
signatures: string | null;
|
|
42
|
+
}
|
|
43
|
+
export default class RequestProvider {
|
|
44
|
+
host: string;
|
|
45
|
+
request: Axios;
|
|
46
|
+
constructor(networkId: string);
|
|
47
|
+
getPendingTransactions(safeAddress: string, nonce: number): Promise<{
|
|
48
|
+
results: SafeTransactionItem[];
|
|
49
|
+
}>;
|
|
50
|
+
postTransactions(safeAddres: string, data: any): Promise<void>;
|
|
51
|
+
getSafeInfo(safeAddress: string): Promise<SafeInfo>;
|
|
52
|
+
confirmTransaction(hash: string, data: any): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
export {};
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { toChecksumAddress } from "web3-utils";
|
|
3
|
+
const HOST_MAP = {
|
|
4
|
+
'1': "https://safe-transaction.gnosis.io/api/v1",
|
|
5
|
+
'137': 'https://safe-transaction.polygon.gnosis.io/api/v1',
|
|
6
|
+
'56': 'https://safe-transaction.bsc.gnosis.io/api/v1',
|
|
7
|
+
'100': 'https://safe-transaction.xdai.gnosis.io/api/v1'
|
|
8
|
+
};
|
|
9
|
+
export default class RequestProvider {
|
|
10
|
+
constructor(networkId) {
|
|
11
|
+
if (!(networkId in HOST_MAP)) {
|
|
12
|
+
throw new Error('Wrong networkId');
|
|
13
|
+
}
|
|
14
|
+
this.host = HOST_MAP[networkId];
|
|
15
|
+
this.request = axios.create({
|
|
16
|
+
baseURL: this.host
|
|
17
|
+
});
|
|
18
|
+
this.request.interceptors.response.use((response) => {
|
|
19
|
+
return response.data;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
getPendingTransactions(safeAddress, nonce) {
|
|
23
|
+
return this.request.get(`/safes/${safeAddress}/multisig-transactions/`, {
|
|
24
|
+
params: {
|
|
25
|
+
executed: false,
|
|
26
|
+
nonce__gte: nonce,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
postTransactions(safeAddres, data) {
|
|
31
|
+
return this.request.post(`/safes/${toChecksumAddress(safeAddres)}/multisig-transactions/`, data);
|
|
32
|
+
}
|
|
33
|
+
getSafeInfo(safeAddress) {
|
|
34
|
+
return this.request.get(`/safes/${safeAddress}/`);
|
|
35
|
+
}
|
|
36
|
+
confirmTransaction(hash, data) {
|
|
37
|
+
return this.request.post(`/multisig-transactions/${hash}/confirmations/`, data);
|
|
38
|
+
}
|
|
39
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Contract } from "ethers";
|
|
2
|
+
import { BigNumber } from "@ethersproject/bignumber";
|
|
3
|
+
import { providers } from "ethers";
|
|
4
|
+
import { SafeTransactionDataPartial, SafeSignature } from "@gnosis.pm/safe-core-sdk-types";
|
|
5
|
+
import { TransactionResult, TransactionOptions } from "@gnosis.pm/safe-core-sdk/dist/src/utils/transactions/types";
|
|
6
|
+
import SafeTransaction from "@gnosis.pm/safe-core-sdk/dist/src/utils/transactions/SafeTransaction";
|
|
7
|
+
import RequestProvider, { SafeInfo } from "./api";
|
|
8
|
+
declare class Safe {
|
|
9
|
+
contract: Contract;
|
|
10
|
+
safeAddress: string;
|
|
11
|
+
owners: string[];
|
|
12
|
+
version: string;
|
|
13
|
+
provider: providers.Web3Provider;
|
|
14
|
+
safeInfo: SafeInfo | null;
|
|
15
|
+
request: RequestProvider;
|
|
16
|
+
network: string;
|
|
17
|
+
constructor(safeAddress: string, version: string, provider: providers.Web3Provider, network?: string);
|
|
18
|
+
static getSafeInfo(safeAddress: string, network: string): Promise<SafeInfo>;
|
|
19
|
+
static getPendingTransactions(safeAddress: string, network: string): Promise<{
|
|
20
|
+
results: import("./api").SafeTransactionItem[];
|
|
21
|
+
}>;
|
|
22
|
+
init(): Promise<void>;
|
|
23
|
+
getOwners(): Promise<string[]>;
|
|
24
|
+
getThreshold(): Promise<any>;
|
|
25
|
+
getNonce(): Promise<number>;
|
|
26
|
+
buildTransaction(data: SafeTransactionDataPartial): Promise<SafeTransaction>;
|
|
27
|
+
getTransactionHash(transaction: SafeTransaction): Promise<any>;
|
|
28
|
+
signTransactionHash(hash: string): Promise<SafeSignature>;
|
|
29
|
+
signTransaction(transaction: SafeTransaction): Promise<void>;
|
|
30
|
+
getOwnersWhoApprovedTx(txHash: string): Promise<string[]>;
|
|
31
|
+
postTransaction(transaction: SafeTransaction, hash: string): Promise<void>;
|
|
32
|
+
confirmTransaction(safeTransaction: SafeTransaction): Promise<void>;
|
|
33
|
+
getBalance(): Promise<BigNumber>;
|
|
34
|
+
executeTransaction(safeTransaction: SafeTransaction, options?: TransactionOptions): Promise<TransactionResult>;
|
|
35
|
+
}
|
|
36
|
+
export default Safe;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { Contract } from "ethers";
|
|
2
|
+
import { BigNumber } from "@ethersproject/bignumber";
|
|
3
|
+
import BN from 'bignumber.js';
|
|
4
|
+
import { getSafeSingletonDeployment } from "@gnosis.pm/safe-deployments";
|
|
5
|
+
import { toChecksumAddress } from "web3-utils";
|
|
6
|
+
import SafeTransaction from "@gnosis.pm/safe-core-sdk/dist/src/utils/transactions/SafeTransaction";
|
|
7
|
+
import RequestProvider from "./api";
|
|
8
|
+
import { standardizeSafeTransactionData, sameString, generateSignature, generatePreValidatedSignature, estimateGasForTransactionExecution, } from "./utils";
|
|
9
|
+
class Safe {
|
|
10
|
+
constructor(safeAddress, version, provider, network = "1") {
|
|
11
|
+
this.owners = [];
|
|
12
|
+
this.safeInfo = null;
|
|
13
|
+
const contract = getSafeSingletonDeployment({
|
|
14
|
+
version,
|
|
15
|
+
network,
|
|
16
|
+
});
|
|
17
|
+
if (!contract) {
|
|
18
|
+
throw new Error("Wrong version or network");
|
|
19
|
+
}
|
|
20
|
+
this.provider = provider;
|
|
21
|
+
this.contract = new Contract(safeAddress, contract.abi, this.provider);
|
|
22
|
+
this.version = version;
|
|
23
|
+
this.safeAddress = safeAddress;
|
|
24
|
+
this.network = network;
|
|
25
|
+
this.request = new RequestProvider(network);
|
|
26
|
+
this.init();
|
|
27
|
+
}
|
|
28
|
+
static getSafeInfo(safeAddress, network) {
|
|
29
|
+
const request = new RequestProvider(network);
|
|
30
|
+
return request.getSafeInfo(toChecksumAddress(safeAddress));
|
|
31
|
+
}
|
|
32
|
+
static async getPendingTransactions(safeAddress, network) {
|
|
33
|
+
const request = new RequestProvider(network);
|
|
34
|
+
const nonce = (await request.getSafeInfo(toChecksumAddress(safeAddress))).nonce;
|
|
35
|
+
const transactions = await request.getPendingTransactions(safeAddress, nonce);
|
|
36
|
+
return transactions;
|
|
37
|
+
}
|
|
38
|
+
async init() {
|
|
39
|
+
const safeInfo = await Safe.getSafeInfo(this.safeAddress, this.network);
|
|
40
|
+
this.safeInfo = safeInfo;
|
|
41
|
+
if (this.version !== safeInfo.version) {
|
|
42
|
+
throw new Error(`Current version ${this.version} not matched address version ${safeInfo.version}`);
|
|
43
|
+
}
|
|
44
|
+
this.version = safeInfo.version;
|
|
45
|
+
this.owners = safeInfo.owners;
|
|
46
|
+
}
|
|
47
|
+
async getOwners() {
|
|
48
|
+
const owners = await this.contract.getOwners();
|
|
49
|
+
return owners;
|
|
50
|
+
}
|
|
51
|
+
async getThreshold() {
|
|
52
|
+
const threshold = await this.contract.getThreshold();
|
|
53
|
+
return threshold.toNumber();
|
|
54
|
+
}
|
|
55
|
+
async getNonce() {
|
|
56
|
+
const nonce = await this.contract.nonce();
|
|
57
|
+
return nonce.toNumber();
|
|
58
|
+
}
|
|
59
|
+
async buildTransaction(data) {
|
|
60
|
+
const transaction = await standardizeSafeTransactionData(this.safeAddress, this.contract, this.provider, data);
|
|
61
|
+
return new SafeTransaction(transaction);
|
|
62
|
+
}
|
|
63
|
+
async getTransactionHash(transaction) {
|
|
64
|
+
const transactionData = transaction.data;
|
|
65
|
+
return this.contract.getTransactionHash(transactionData.to, transactionData.value, transactionData.data, transactionData.operation, transactionData.safeTxGas, transactionData.baseGas, transactionData.gasPrice, transactionData.gasToken, transactionData.refundReceiver, transactionData.nonce);
|
|
66
|
+
}
|
|
67
|
+
async signTransactionHash(hash) {
|
|
68
|
+
const owners = await this.getOwners();
|
|
69
|
+
const signer = await this.provider.getSigner(0);
|
|
70
|
+
const signerAddress = await signer.getAddress();
|
|
71
|
+
const addressIsOwner = owners.find((owner) => signerAddress && sameString(owner, signerAddress));
|
|
72
|
+
if (!addressIsOwner) {
|
|
73
|
+
throw new Error("Transactions can only be signed by Safe owners");
|
|
74
|
+
}
|
|
75
|
+
return generateSignature(this.provider, hash);
|
|
76
|
+
}
|
|
77
|
+
async signTransaction(transaction) {
|
|
78
|
+
const hash = await this.getTransactionHash(transaction);
|
|
79
|
+
const sig = await this.signTransactionHash(hash);
|
|
80
|
+
transaction.addSignature(sig);
|
|
81
|
+
}
|
|
82
|
+
async getOwnersWhoApprovedTx(txHash) {
|
|
83
|
+
const owners = await this.getOwners();
|
|
84
|
+
let ownersWhoApproved = [];
|
|
85
|
+
for (const owner of owners) {
|
|
86
|
+
const approved = await this.contract.approvedHashes(owner, txHash);
|
|
87
|
+
if (approved.gt(0)) {
|
|
88
|
+
ownersWhoApproved.push(owner);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return ownersWhoApproved;
|
|
92
|
+
}
|
|
93
|
+
async postTransaction(transaction, hash) {
|
|
94
|
+
const signer = this.provider.getSigner(0);
|
|
95
|
+
const signerAddress = await signer.getAddress();
|
|
96
|
+
const safeAddress = toChecksumAddress(this.safeAddress);
|
|
97
|
+
await this.request.postTransactions(this.safeAddress, {
|
|
98
|
+
safe: safeAddress,
|
|
99
|
+
to: toChecksumAddress(transaction.data.to),
|
|
100
|
+
value: new BN(transaction.data.value).toFixed(),
|
|
101
|
+
data: transaction.data.data,
|
|
102
|
+
operation: transaction.data.operation,
|
|
103
|
+
gasToken: transaction.data.gasToken,
|
|
104
|
+
safeTxGas: transaction.data.safeTxGas,
|
|
105
|
+
baseGas: transaction.data.baseGas,
|
|
106
|
+
gasPrice: transaction.data.gasPrice,
|
|
107
|
+
refundReceiver: transaction.data.refundReceiver,
|
|
108
|
+
nonce: transaction.data.nonce,
|
|
109
|
+
contractTransactionHash: hash,
|
|
110
|
+
sender: toChecksumAddress(signerAddress),
|
|
111
|
+
signature: transaction.encodedSignatures(),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
async confirmTransaction(safeTransaction) {
|
|
115
|
+
const hash = await this.getTransactionHash(safeTransaction);
|
|
116
|
+
const signature = await this.signTransactionHash(hash);
|
|
117
|
+
safeTransaction.addSignature(signature);
|
|
118
|
+
const signer = await this.provider.getSigner(0);
|
|
119
|
+
const signerAddress = await signer.getAddress();
|
|
120
|
+
const sig = safeTransaction.signatures.get(signerAddress === null || signerAddress === void 0 ? void 0 : signerAddress.toLowerCase());
|
|
121
|
+
if (sig) {
|
|
122
|
+
await this.request.confirmTransaction(hash, { signature: sig.data });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async getBalance() {
|
|
126
|
+
return this.provider.getBalance(this.safeAddress);
|
|
127
|
+
}
|
|
128
|
+
async executeTransaction(safeTransaction, options) {
|
|
129
|
+
const txHash = await this.getTransactionHash(safeTransaction);
|
|
130
|
+
const ownersWhoApprovedTx = await this.getOwnersWhoApprovedTx(txHash);
|
|
131
|
+
for (const owner of ownersWhoApprovedTx) {
|
|
132
|
+
safeTransaction.addSignature(generatePreValidatedSignature(owner));
|
|
133
|
+
}
|
|
134
|
+
const owners = await this.getOwners();
|
|
135
|
+
const signer = await this.provider.getSigner(0);
|
|
136
|
+
const contract = this.contract.connect(signer);
|
|
137
|
+
const signerAddress = await signer.getAddress();
|
|
138
|
+
if (owners.includes(signerAddress)) {
|
|
139
|
+
safeTransaction.addSignature(generatePreValidatedSignature(signerAddress));
|
|
140
|
+
}
|
|
141
|
+
const threshold = await this.getThreshold();
|
|
142
|
+
if (threshold > safeTransaction.signatures.size) {
|
|
143
|
+
const signaturesMissing = threshold - safeTransaction.signatures.size;
|
|
144
|
+
throw new Error(`There ${signaturesMissing > 1 ? "are" : "is"} ${signaturesMissing} signature${signaturesMissing > 1 ? "s" : ""} missing`);
|
|
145
|
+
}
|
|
146
|
+
const value = BigNumber.from(safeTransaction.data.value);
|
|
147
|
+
if (!value.isZero()) {
|
|
148
|
+
const balance = await this.getBalance();
|
|
149
|
+
if (value.gt(BigNumber.from(balance))) {
|
|
150
|
+
throw new Error("Not enough Ether funds");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const gasLimit = await estimateGasForTransactionExecution(contract, signerAddress, safeTransaction);
|
|
154
|
+
const executionOptions = {
|
|
155
|
+
gasLimit,
|
|
156
|
+
gasPrice: options === null || options === void 0 ? void 0 : options.gasPrice,
|
|
157
|
+
from: signerAddress,
|
|
158
|
+
};
|
|
159
|
+
const txResponse = await contract.execTransaction(safeTransaction.data.to, safeTransaction.data.value, safeTransaction.data.data, safeTransaction.data.operation, safeTransaction.data.safeTxGas, safeTransaction.data.baseGas, safeTransaction.data.gasPrice, safeTransaction.data.gasToken, safeTransaction.data.refundReceiver, safeTransaction.encodedSignatures(), executionOptions);
|
|
160
|
+
return txResponse;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
export default Safe;
|
package/dist/type.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BigNumber } from "@ethersproject/bignumber";
|
|
2
|
+
import { SafeTransaction, SafeTransactionData } from "@gnosis.pm/safe-core-sdk-types";
|
|
3
|
+
import { TransactionOptions, TransactionResult } from "@gnosis.pm/safe-core-sdk/dist/src/utils/transactions/types";
|
|
4
|
+
export interface GnosisSafeContract {
|
|
5
|
+
getVersion(): Promise<string>;
|
|
6
|
+
getAddress(): string;
|
|
7
|
+
getNonce(): Promise<number>;
|
|
8
|
+
getThreshold(): Promise<number>;
|
|
9
|
+
getOwners(): Promise<string[]>;
|
|
10
|
+
isOwner(address: string): Promise<boolean>;
|
|
11
|
+
getTransactionHash(safeTransactionData: SafeTransactionData): Promise<string>;
|
|
12
|
+
approvedHashes(ownerAddress: string, hash: string): Promise<BigNumber>;
|
|
13
|
+
approveHash(hash: string, options?: TransactionOptions): Promise<TransactionResult>;
|
|
14
|
+
getModules(): Promise<string[]>;
|
|
15
|
+
isModuleEnabled(moduleAddress: string): Promise<boolean>;
|
|
16
|
+
execTransaction(safeTransaction: SafeTransaction, options?: TransactionOptions): Promise<TransactionResult>;
|
|
17
|
+
encode(methodName: any, params: any): string;
|
|
18
|
+
estimateGas(methodName: string, params: any[], options: TransactionOptions): Promise<number>;
|
|
19
|
+
}
|
package/dist/type.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Contract, providers } from "ethers";
|
|
2
|
+
import { OperationType, SafeTransactionData, SafeTransactionDataPartial, SafeSignature, SafeTransaction } from "@gnosis.pm/safe-core-sdk-types";
|
|
3
|
+
import EthSignSignature from "@gnosis.pm/safe-core-sdk/dist/src/utils/signatures/SafeSignature";
|
|
4
|
+
export declare function sameString(str1: string, str2: string): boolean;
|
|
5
|
+
export declare function isRestrictedAddress(address: string): boolean;
|
|
6
|
+
export declare function estimateTxGas(safeAddress: string, safeContract: Contract, provider: providers.Web3Provider, to: string, valueInWei: string, data: string, operation: OperationType): Promise<number>;
|
|
7
|
+
export declare function standardizeSafeTransactionData(safeAddress: string, safeContract: Contract, provider: any, tx: SafeTransactionDataPartial): Promise<SafeTransactionData>;
|
|
8
|
+
export declare function generatePreValidatedSignature(ownerAddress: string): SafeSignature;
|
|
9
|
+
export declare function isTxHashSignedWithPrefix(txHash: string, signature: string, ownerAddress: string): boolean;
|
|
10
|
+
export declare function adjustVInSignature(signature: string, hasPrefix: boolean): string;
|
|
11
|
+
export declare function generateSignature(provider: providers.Web3Provider, hash: string): Promise<EthSignSignature>;
|
|
12
|
+
export declare function estimateGasForTransactionExecution(safeContract: Contract, from: string, tx: SafeTransaction): Promise<number>;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { BigNumber } from "@ethersproject/bignumber";
|
|
2
|
+
import { OperationType, } from "@gnosis.pm/safe-core-sdk-types";
|
|
3
|
+
import { bufferToHex, ecrecover, pubToAddress } from "ethereumjs-util";
|
|
4
|
+
import { ZERO_ADDRESS, SENTINEL_ADDRESS } from "./constants";
|
|
5
|
+
import EthSignSignature from "@gnosis.pm/safe-core-sdk/dist/src/utils/signatures/SafeSignature";
|
|
6
|
+
function estimateDataGasCosts(data) {
|
|
7
|
+
const reducer = (accumulator, currentValue) => {
|
|
8
|
+
if (currentValue === "0x") {
|
|
9
|
+
return accumulator + 0;
|
|
10
|
+
}
|
|
11
|
+
if (currentValue === "00") {
|
|
12
|
+
return accumulator + 4;
|
|
13
|
+
}
|
|
14
|
+
return accumulator + 16;
|
|
15
|
+
};
|
|
16
|
+
return data.match(/.{2}/g).reduce(reducer, 0);
|
|
17
|
+
}
|
|
18
|
+
export function sameString(str1, str2) {
|
|
19
|
+
return str1.toLowerCase() === str2.toLowerCase();
|
|
20
|
+
}
|
|
21
|
+
function isZeroAddress(address) {
|
|
22
|
+
return address === ZERO_ADDRESS;
|
|
23
|
+
}
|
|
24
|
+
function isSentinelAddress(address) {
|
|
25
|
+
return address === SENTINEL_ADDRESS;
|
|
26
|
+
}
|
|
27
|
+
export function isRestrictedAddress(address) {
|
|
28
|
+
return isZeroAddress(address) || isSentinelAddress(address);
|
|
29
|
+
}
|
|
30
|
+
export async function estimateTxGas(safeAddress, safeContract, provider, to, valueInWei, data, operation) {
|
|
31
|
+
let txGasEstimation = 0;
|
|
32
|
+
const estimateData = safeContract.interface.encodeFunctionData("requiredTxGas", [to, valueInWei, data, operation]);
|
|
33
|
+
try {
|
|
34
|
+
const estimateResponse = (await provider.estimateGas({
|
|
35
|
+
to: safeAddress,
|
|
36
|
+
from: safeAddress,
|
|
37
|
+
data: estimateData,
|
|
38
|
+
})).toString();
|
|
39
|
+
txGasEstimation =
|
|
40
|
+
BigNumber.from("0x" + estimateResponse.substring(138)).toNumber() + 10000;
|
|
41
|
+
}
|
|
42
|
+
catch (error) { }
|
|
43
|
+
if (txGasEstimation > 0) {
|
|
44
|
+
const dataGasEstimation = estimateDataGasCosts(estimateData);
|
|
45
|
+
let additionalGas = 10000;
|
|
46
|
+
for (let i = 0; i < 10; i++) {
|
|
47
|
+
try {
|
|
48
|
+
const estimateResponse = await provider.call({
|
|
49
|
+
to: safeAddress,
|
|
50
|
+
from: safeAddress,
|
|
51
|
+
data: estimateData,
|
|
52
|
+
gasPrice: 0,
|
|
53
|
+
gasLimit: txGasEstimation + dataGasEstimation + additionalGas,
|
|
54
|
+
});
|
|
55
|
+
if (estimateResponse !== "0x") {
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (error) { }
|
|
60
|
+
txGasEstimation += additionalGas;
|
|
61
|
+
additionalGas *= 2;
|
|
62
|
+
}
|
|
63
|
+
return txGasEstimation + additionalGas;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const estimateGas = await provider.estimateGas({
|
|
67
|
+
to,
|
|
68
|
+
from: safeAddress,
|
|
69
|
+
value: valueInWei,
|
|
70
|
+
data,
|
|
71
|
+
});
|
|
72
|
+
return estimateGas.toNumber();
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (operation === OperationType.DelegateCall) {
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
return Promise.reject(error);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export async function standardizeSafeTransactionData(safeAddress, safeContract, provider, tx) {
|
|
82
|
+
var _a, _b, _c, _d, _e;
|
|
83
|
+
const standardizedTxs = {
|
|
84
|
+
to: tx.to,
|
|
85
|
+
value: tx.value,
|
|
86
|
+
data: tx.data,
|
|
87
|
+
operation: (_a = tx.operation) !== null && _a !== void 0 ? _a : OperationType.Call,
|
|
88
|
+
baseGas: (_b = tx.baseGas) !== null && _b !== void 0 ? _b : 0,
|
|
89
|
+
gasPrice: (_c = tx.gasPrice) !== null && _c !== void 0 ? _c : 0,
|
|
90
|
+
gasToken: tx.gasToken || ZERO_ADDRESS,
|
|
91
|
+
refundReceiver: tx.refundReceiver || ZERO_ADDRESS,
|
|
92
|
+
nonce: (_d = tx.nonce) !== null && _d !== void 0 ? _d : (await safeContract.nonce()).toNumber(),
|
|
93
|
+
};
|
|
94
|
+
const safeTxGas = (_e = tx.safeTxGas) !== null && _e !== void 0 ? _e : (await estimateTxGas(safeAddress, safeContract, provider, standardizedTxs.to, standardizedTxs.value, standardizedTxs.data, standardizedTxs.operation));
|
|
95
|
+
return {
|
|
96
|
+
...standardizedTxs,
|
|
97
|
+
safeTxGas,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function generatePreValidatedSignature(ownerAddress) {
|
|
101
|
+
const signature = "0x000000000000000000000000" +
|
|
102
|
+
ownerAddress.slice(2) +
|
|
103
|
+
"0000000000000000000000000000000000000000000000000000000000000000" +
|
|
104
|
+
"01";
|
|
105
|
+
return new EthSignSignature(ownerAddress, signature);
|
|
106
|
+
}
|
|
107
|
+
export function isTxHashSignedWithPrefix(txHash, signature, ownerAddress) {
|
|
108
|
+
let hasPrefix;
|
|
109
|
+
try {
|
|
110
|
+
const rsvSig = {
|
|
111
|
+
r: Buffer.from(signature.slice(2, 66), "hex"),
|
|
112
|
+
s: Buffer.from(signature.slice(66, 130), "hex"),
|
|
113
|
+
v: parseInt(signature.slice(130, 132), 16),
|
|
114
|
+
};
|
|
115
|
+
const recoveredData = ecrecover(Buffer.from(txHash.slice(2), "hex"), rsvSig.v, rsvSig.r, rsvSig.s);
|
|
116
|
+
const recoveredAddress = bufferToHex(pubToAddress(recoveredData));
|
|
117
|
+
hasPrefix = !sameString(recoveredAddress, ownerAddress);
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
hasPrefix = true;
|
|
121
|
+
}
|
|
122
|
+
return hasPrefix;
|
|
123
|
+
}
|
|
124
|
+
export function adjustVInSignature(signature, hasPrefix) {
|
|
125
|
+
const V_VALUES = [0, 1, 27, 28];
|
|
126
|
+
const MIN_VALID_V_VALUE = 27;
|
|
127
|
+
let signatureV = parseInt(signature.slice(-2), 16);
|
|
128
|
+
if (!V_VALUES.includes(signatureV)) {
|
|
129
|
+
throw new Error("Invalid signature");
|
|
130
|
+
}
|
|
131
|
+
if (signatureV < MIN_VALID_V_VALUE) {
|
|
132
|
+
signatureV += MIN_VALID_V_VALUE;
|
|
133
|
+
}
|
|
134
|
+
if (hasPrefix) {
|
|
135
|
+
signatureV += 4;
|
|
136
|
+
}
|
|
137
|
+
signature = signature.slice(0, -2) + signatureV.toString(16);
|
|
138
|
+
return signature;
|
|
139
|
+
}
|
|
140
|
+
export async function generateSignature(provider, hash) {
|
|
141
|
+
const signer = await provider.getSigner(0);
|
|
142
|
+
const signerAddress = await signer.getAddress();
|
|
143
|
+
let signature = await provider.send("personal_sign", [hash, signerAddress]);
|
|
144
|
+
const hasPrefix = isTxHashSignedWithPrefix(hash, signature, signerAddress);
|
|
145
|
+
signature = adjustVInSignature(signature, hasPrefix);
|
|
146
|
+
return new EthSignSignature(signerAddress, signature);
|
|
147
|
+
}
|
|
148
|
+
export async function estimateGasForTransactionExecution(safeContract, from, tx) {
|
|
149
|
+
try {
|
|
150
|
+
const gas = await safeContract.estimateGas.execTransaction(tx.data.to, tx.data.value, tx.data.data, tx.data.operation, tx.data.safeTxGas, tx.data.baseGas, tx.data.gasPrice, tx.data.gasToken, tx.data.refundReceiver, tx.encodedSignatures(), { from });
|
|
151
|
+
return gas.toNumber();
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
return Promise.reject(error);
|
|
155
|
+
}
|
|
156
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rabby-wallet/gnosis-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "",
|
|
5
|
-
"main": "index.js",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "tsc"
|
|
8
8
|
},
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"@gnosis.pm/safe-core-sdk-types": "^0.1.1",
|
|
19
19
|
"@gnosis.pm/safe-deployments": "^1.4.0",
|
|
20
20
|
"axios": "^0.24.0",
|
|
21
|
+
"bignumber.js": "^9.0.2",
|
|
21
22
|
"ethereumjs-util": "^7.1.3",
|
|
22
23
|
"ethers": "^5.5.1",
|
|
23
24
|
"typescript": "^4.4.4",
|
package/src/api.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Contract } from "ethers";
|
|
2
2
|
import { BigNumber } from "@ethersproject/bignumber";
|
|
3
|
+
import BN from 'bignumber.js';
|
|
3
4
|
import { getSafeSingletonDeployment } from "@gnosis.pm/safe-deployments";
|
|
4
5
|
import { providers } from "ethers";
|
|
5
6
|
import { toChecksumAddress } from "web3-utils";
|
|
@@ -19,7 +20,6 @@ import {
|
|
|
19
20
|
generateSignature,
|
|
20
21
|
generatePreValidatedSignature,
|
|
21
22
|
estimateGasForTransactionExecution,
|
|
22
|
-
toTxResult,
|
|
23
23
|
} from "./utils";
|
|
24
24
|
|
|
25
25
|
class Safe {
|
|
@@ -56,7 +56,18 @@ class Safe {
|
|
|
56
56
|
|
|
57
57
|
static getSafeInfo(safeAddress: string, network: string) {
|
|
58
58
|
const request = new RequestProvider(network);
|
|
59
|
-
return request.getSafeInfo(safeAddress);
|
|
59
|
+
return request.getSafeInfo(toChecksumAddress(safeAddress));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static async getPendingTransactions(safeAddress: string, network: string) {
|
|
63
|
+
const request = new RequestProvider(network);
|
|
64
|
+
const nonce = (await request.getSafeInfo(toChecksumAddress(safeAddress))).nonce;
|
|
65
|
+
const transactions = await request.getPendingTransactions(
|
|
66
|
+
safeAddress,
|
|
67
|
+
nonce
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
return transactions;
|
|
60
71
|
}
|
|
61
72
|
|
|
62
73
|
async init() {
|
|
@@ -87,13 +98,6 @@ class Safe {
|
|
|
87
98
|
return nonce.toNumber();
|
|
88
99
|
}
|
|
89
100
|
|
|
90
|
-
async getPendingTransactions() {
|
|
91
|
-
const nonce = await this.getNonce();
|
|
92
|
-
const transactions = await this.request.getPendingTransactions(this.safeAddress, nonce);
|
|
93
|
-
|
|
94
|
-
return transactions;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
101
|
async buildTransaction(data: SafeTransactionDataPartial) {
|
|
98
102
|
const transaction = await standardizeSafeTransactionData(
|
|
99
103
|
this.safeAddress,
|
|
@@ -158,7 +162,7 @@ class Safe {
|
|
|
158
162
|
await this.request.postTransactions(this.safeAddress, {
|
|
159
163
|
safe: safeAddress,
|
|
160
164
|
to: toChecksumAddress(transaction.data.to),
|
|
161
|
-
value:
|
|
165
|
+
value: new BN(transaction.data.value).toFixed(),
|
|
162
166
|
data: transaction.data.data,
|
|
163
167
|
operation: transaction.data.operation,
|
|
164
168
|
gasToken: transaction.data.gasToken,
|
|
@@ -251,9 +255,9 @@ class Safe {
|
|
|
251
255
|
safeTransaction.data.refundReceiver,
|
|
252
256
|
safeTransaction.encodedSignatures(),
|
|
253
257
|
executionOptions
|
|
254
|
-
)
|
|
258
|
+
);
|
|
255
259
|
|
|
256
|
-
return
|
|
260
|
+
return txResponse;
|
|
257
261
|
}
|
|
258
262
|
}
|
|
259
263
|
|
package/src/utils.ts
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
import { BigNumber } from "@ethersproject/bignumber";
|
|
2
2
|
import { Contract, providers } from "ethers";
|
|
3
3
|
import {
|
|
4
|
-
MetaTransactionData,
|
|
5
4
|
OperationType,
|
|
6
5
|
SafeTransactionData,
|
|
7
6
|
SafeTransactionDataPartial,
|
|
8
7
|
SafeSignature,
|
|
9
8
|
SafeTransaction,
|
|
10
9
|
} from "@gnosis.pm/safe-core-sdk-types";
|
|
11
|
-
import {
|
|
12
|
-
TransactionOptions,
|
|
13
|
-
Web3TransactionResult,
|
|
14
|
-
} from "@gnosis.pm/safe-core-sdk/dist/src/utils/transactions/types";
|
|
15
|
-
import { PromiEvent, TransactionReceipt } from "web3-core/types";
|
|
16
10
|
import { bufferToHex, ecrecover, pubToAddress } from "ethereumjs-util";
|
|
17
11
|
import { ZERO_ADDRESS, SENTINEL_ADDRESS } from "./constants";
|
|
18
12
|
import EthSignSignature from "@gnosis.pm/safe-core-sdk/dist/src/utils/signatures/SafeSignature";
|
|
@@ -236,16 +230,3 @@ export async function estimateGasForTransactionExecution(
|
|
|
236
230
|
return Promise.reject(error);
|
|
237
231
|
}
|
|
238
232
|
}
|
|
239
|
-
|
|
240
|
-
export function toTxResult(
|
|
241
|
-
promiEvent: PromiEvent<TransactionReceipt>,
|
|
242
|
-
options?: TransactionOptions
|
|
243
|
-
): Promise<Web3TransactionResult> {
|
|
244
|
-
return new Promise((resolve, reject) =>
|
|
245
|
-
promiEvent
|
|
246
|
-
.once("transactionHash", (hash: string) =>
|
|
247
|
-
resolve({ hash, promiEvent, options })
|
|
248
|
-
)
|
|
249
|
-
.catch(reject)
|
|
250
|
-
);
|
|
251
|
-
}
|
package/tsconfig.json
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"allowSyntheticDefaultImports": true,
|
|
13
13
|
"allowJs": true,
|
|
14
14
|
"plugins": [{ "transform": "typescript-transform-paths", "afterDeclarations": true }],
|
|
15
|
-
"outDir": "./dist"
|
|
15
|
+
"outDir": "./dist",
|
|
16
|
+
"declaration": true
|
|
16
17
|
},
|
|
17
18
|
"exclude": ["./node_modules"],
|
|
18
19
|
"include": ["src", "__tests__"]
|