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

Merge development into main #308

Merged
merged 19 commits into from
Dec 2, 2022
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
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,16 @@ Software developer tools that facilitate the interaction with the Safe [contract

## Playground

There is a [playground script](https://github.com/safe-global/safe-core-sdk/tree/main/playground/index.ts) that can be used to play around with the Safe Core SDK.
This project includes a [playground](https://github.com/safe-global/safe-core-sdk/tree/main/playground) with a few scripts that can be used as a starting point to use the Safe Core SDK. These scripts do not cover all the functionality exposed by the SDK, but each of them present steps of the Safe transaction flow.

Update the config inside the script and execute the following command to run the script:
Update the config inside the scripts and execute the following commands to run each step:

#### Step 1: Deploy a Safe
```bash
yarn play
yarn play deploy-safe
```

#### Step 2: Propose a transaction
```bash
yarn play propose-transaction
```
23 changes: 1 addition & 22 deletions guides/integrating-the-safe-core-sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,28 +301,7 @@ Once there are enough confirmations in the service the transaction is ready to b
The method `executeTransaction` accepts an instance of the class `SafeTransaction` so the transaction needs to be transformed from the type `SafeMultisigTransactionResponse`.

```js
import { EthSignSignature } from '@safe-global/safe-core-sdk'

// transaction: SafeMultisigTransactionResponse

const safeTransactionData: SafeTransactionData = {
to: transaction.to,
value: transaction.value,
data: transaction.data,
operation: transaction.operation,
safeTxGas: transaction.safeTxGas,
baseGas: transaction.baseGas,
gasPrice: transaction.gasPrice,
gasToken: transaction.gasToken,
refundReceiver: transaction.refundReceiver,
nonce: transaction.nonce
}
const safeTransaction = await safeSdk.createTransaction({ safeTransactionData })
transaction.confirmations.forEach(confirmation => {
const signature = new EthSignSignature(confirmation.owner, confirmation.signature)
safeTransaction.addSignature(signature)
})

const safeTransaction = await safeService.getTransaction(...)
const executeTxResponse = await safeSdk.executeTransaction(safeTransaction)
const receipt = executeTxResponse.transactionResponse && (await executeTxResponse.transactionResponse.wait())
```
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"build": "lerna run build --stream",
"test": "FORCE_COLOR=1 lerna run test --stream",
"test:ci": "FORCE_COLOR=1 lerna run test:ci --stream",
"play": "ts-node ./playground",
"format": "lerna run format",
"play": "ts-node ./playground/config/run.ts",
"format": "lerna run format && prettier --write \"playground/**/*.ts\"",
"postinstall": "cd packages/safe-ethers-lib; hardhat compile"
},
"workspaces": {
Expand All @@ -20,6 +20,7 @@
"license": "MIT",
"devDependencies": {
"lerna": "^6.0.3",
"rimraf": "^3.0.2"
"rimraf": "^3.0.2",
"ts-node": "^10.9.1"
}
}
47 changes: 47 additions & 0 deletions packages/safe-core-sdk-types/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,50 @@ export interface GenerateTypedData {
nonce: BigNumber
}
}

export type SafeMultisigConfirmationResponse = {
readonly owner: string
readonly submissionDate: string
readonly transactionHash?: string
readonly confirmationType?: string
readonly signature: string
readonly signatureType?: string
}

export type SafeMultisigConfirmationListResponse = {
readonly count: number
readonly next?: string
readonly previous?: string
readonly results: SafeMultisigConfirmationResponse[]
}

export type SafeMultisigTransactionResponse = {
readonly safe: string
readonly to: string
readonly value: string
readonly data?: string
readonly operation: number
readonly gasToken: string
readonly safeTxGas: number
readonly baseGas: number
readonly gasPrice: string
readonly refundReceiver?: string
readonly nonce: number
readonly executionDate: string
readonly submissionDate: string
readonly modified: string
readonly blockNumber?: number
readonly transactionHash: string
readonly safeTxHash: string
readonly executor?: string
readonly isExecuted: boolean
readonly isSuccessful?: boolean
readonly ethGasPrice?: string
readonly gasUsed?: number
readonly fee?: number
readonly origin: string
readonly dataDecoded?: string
readonly confirmationsRequired: number
readonly confirmations?: SafeMultisigConfirmationResponse[]
readonly signatures?: string
}
1 change: 0 additions & 1 deletion packages/safe-core-sdk-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
"mocha": "^10.1.0",
"nyc": "^15.1.0",
"prettier": "^2.8.0",
"ts-node": "^10.9.1",
"typescript": "^4.9.3"
},
"dependencies": {
Expand Down
1 change: 0 additions & 1 deletion packages/safe-core-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@
"nyc": "^15.1.0",
"prettier": "^2.8.0",
"ts-generator": "^0.1.1",
"ts-node": "^10.9.1",
"typescript": "^4.9.3",
"web3": "^1.8.1",
"yargs": "^17.6.2"
Expand Down
64 changes: 53 additions & 11 deletions packages/safe-core-sdk/src/Safe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
EthAdapter,
MetaTransactionData,
OperationType,
SafeSignature,
SafeMultisigTransactionResponse,
SafeTransaction,
SafeTransactionDataPartial,
SafeTransactionEIP712Args,
Expand All @@ -17,12 +17,13 @@ import GuardManager from './managers/guardManager'
import ModuleManager from './managers/moduleManager'
import OwnerManager from './managers/ownerManager'
import { ContractNetworksConfig } from './types'
import { isMetaTransactionArray, sameString } from './utils'
import { isMetaTransactionArray, isSafeMultisigTransactionResponse, sameString } from './utils'
import {
generateEIP712Signature,
generatePreValidatedSignature,
generateSignature
} from './utils/signatures'
import SafeSignature from './utils/signatures/SafeSignature'
import EthSafeTransaction from './utils/transactions/SafeTransaction'
import { SafeTransactionOptionalProps } from './utils/transactions/types'
import {
Expand Down Expand Up @@ -436,9 +437,13 @@ class Safe {
* @throws "Transactions can only be signed by Safe owners"
*/
async signTransaction(
safeTransaction: SafeTransaction,
safeTransaction: SafeTransaction | SafeMultisigTransactionResponse,
signingMethod: 'eth_sign' | 'eth_signTypedData' = 'eth_sign'
): Promise<SafeTransaction> {
let transaction = isSafeMultisigTransactionResponse(safeTransaction)
? await this.toSafeTransactionType(safeTransaction)
: safeTransaction

const owners = await this.getOwners()
const signerAddress = await this.#ethAdapter.getSignerAddress()
if (!signerAddress) {
Expand All @@ -452,15 +457,15 @@ class Safe {
}
let signature: SafeSignature
if (signingMethod === 'eth_signTypedData') {
signature = await this.signTypedData(safeTransaction)
signature = await this.signTypedData(transaction)
} else {
const txHash = await this.getTransactionHash(safeTransaction)
const txHash = await this.getTransactionHash(transaction)
signature = await this.signTransactionHash(txHash)
}
const signedSafeTransaction = await this.createTransaction({
safeTransactionData: safeTransaction.data
safeTransactionData: transaction.data
})
safeTransaction.signatures.forEach((signature) => {
transaction.signatures.forEach((signature) => {
signedSafeTransaction.addSignature(signature)
})
signedSafeTransaction.addSignature(signature)
Expand Down Expand Up @@ -752,6 +757,35 @@ class Safe {
return safeTransaction
}

/**
* Converts a transaction from type SafeMultisigTransactionResponse to type SafeTransaction
*
* @param serviceTransactionResponse - The transaction to convert
* @returns The converted transaction with type SafeTransaction
*/
async toSafeTransactionType(
serviceTransactionResponse: SafeMultisigTransactionResponse
): Promise<SafeTransaction> {
const safeTransactionData: SafeTransactionDataPartial = {
to: serviceTransactionResponse.to,
value: serviceTransactionResponse.value,
data: serviceTransactionResponse.data || '0x',
operation: serviceTransactionResponse.operation,
safeTxGas: serviceTransactionResponse.safeTxGas,
baseGas: serviceTransactionResponse.baseGas,
gasPrice: Number(serviceTransactionResponse.gasPrice),
gasToken: serviceTransactionResponse.gasToken,
refundReceiver: serviceTransactionResponse.refundReceiver,
nonce: serviceTransactionResponse.nonce
}
const safeTransaction = await this.createTransaction({ safeTransactionData })
serviceTransactionResponse.confirmations?.map((confirmation) => {
const signature = new SafeSignature(confirmation.owner, confirmation.signature)
safeTransaction.addSignature(signature)
})
return safeTransaction
}

/**
* Checks if a Safe transaction can be executed successfully with no errors.
*
Expand All @@ -760,10 +794,14 @@ class Safe {
* @returns TRUE if the Safe transaction can be executed successfully with no errors
*/
async isValidTransaction(
safeTransaction: SafeTransaction,
safeTransaction: SafeTransaction | SafeMultisigTransactionResponse,
options?: TransactionOptions
): Promise<boolean> {
const signedSafeTransaction = await this.copyTransaction(safeTransaction)
let transaction = isSafeMultisigTransactionResponse(safeTransaction)
? await this.toSafeTransactionType(safeTransaction)
: safeTransaction

const signedSafeTransaction = await this.copyTransaction(transaction)

const txHash = await this.getTransactionHash(signedSafeTransaction)
const ownersWhoApprovedTx = await this.getOwnersWhoApprovedTx(txHash)
Expand Down Expand Up @@ -800,10 +838,14 @@ class Safe {
* @throws "Cannot specify gas and gasLimit together in transaction options"
*/
async executeTransaction(
safeTransaction: SafeTransaction,
safeTransaction: SafeTransaction | SafeMultisigTransactionResponse,
options?: TransactionOptions
): Promise<TransactionResult> {
const signedSafeTransaction = await this.copyTransaction(safeTransaction)
let transaction = isSafeMultisigTransactionResponse(safeTransaction)
? await this.toSafeTransactionType(safeTransaction)
: safeTransaction

const signedSafeTransaction = await this.copyTransaction(transaction)

const txHash = await this.getTransactionHash(signedSafeTransaction)
const ownersWhoApprovedTx = await this.getOwnersWhoApprovedTx(txHash)
Expand Down
2 changes: 0 additions & 2 deletions packages/safe-core-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import SafeFactory, {
SafeFactoryConfig
} from './safeFactory'
import { ContractNetworksConfig } from './types'
import EthSignSignature from './utils/signatures/SafeSignature'
import { SafeTransactionOptionalProps } from './utils/transactions/types'
import { standardizeSafeTransactionData } from './utils/transactions/utils'

Expand All @@ -36,6 +35,5 @@ export {
AddOwnerTxParams,
RemoveOwnerTxParams,
SwapOwnerTxParams,
EthSignSignature,
standardizeSafeTransactionData
}
13 changes: 12 additions & 1 deletion packages/safe-core-sdk/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { MetaTransactionData, SafeTransactionDataPartial } from '@safe-global/safe-core-sdk-types'
import {
MetaTransactionData,
SafeMultisigTransactionResponse,
SafeTransaction,
SafeTransactionDataPartial
} from '@safe-global/safe-core-sdk-types'
import { SENTINEL_ADDRESS, ZERO_ADDRESS } from './constants'

export function sameString(str1: string, str2: string): boolean {
Expand All @@ -22,3 +27,9 @@ export function isMetaTransactionArray(
): safeTransactions is MetaTransactionData[] {
return (safeTransactions as MetaTransactionData[])?.length !== undefined
}

export function isSafeMultisigTransactionResponse(
safeTransaction: SafeTransaction | SafeMultisigTransactionResponse
): safeTransaction is SafeMultisigTransactionResponse {
return (safeTransaction as SafeMultisigTransactionResponse).isExecuted !== undefined
}
71 changes: 70 additions & 1 deletion packages/safe-core-sdk/tests/offChainSignatures.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { SafeTransactionDataPartial } from '@safe-global/safe-core-sdk-types'
import {
SafeMultisigTransactionResponse,
SafeTransactionDataPartial
} from '@safe-global/safe-core-sdk-types'
import chai from 'chai'
import chaiAsPromised from 'chai-as-promised'
import { deployments, waffle } from 'hardhat'
Expand Down Expand Up @@ -109,5 +112,71 @@ describe('Off-chain signatures', () => {
chai.expect(signedTx2.signatures.size).to.be.eq(1)
chai.expect(tx.signatures.size).to.be.eq(0)
})

it('should sign a transaction received from the Safe Transaction Service', async () => {
const { safe, accounts, contractNetworks } = await setupTests()
const [account1, account2] = accounts
const ethAdapter = await getEthAdapter(account1.signer)
const safeSdk = await Safe.create({
ethAdapter,
safeAddress: safe.address,
contractNetworks
})
const safeServiceTransaction: SafeMultisigTransactionResponse = {
safe: '',
to: account2.address,
value: '500000000000000000', // 0.5 ETH
data: '0x',
operation: 1,
gasToken: '0x3333333333333333333333333333333333333333',
safeTxGas: 666,
baseGas: 111,
gasPrice: '222',
refundReceiver: '0x4444444444444444444444444444444444444444',
nonce: await safeSdk.getNonce(),
executionDate: '',
submissionDate: '',
modified: '',
blockNumber: 12345,
transactionHash: '',
safeTxHash: '',
executor: '',
isExecuted: false,
isSuccessful: false,
ethGasPrice: '',
gasUsed: 12345,
fee: 12345,
origin: '',
dataDecoded: '',
confirmationsRequired: 2,
confirmations: [
{
owner: '0x1111111111111111111111111111111111111111',
submissionDate: '',
transactionHash: '',
confirmationType: '',
signature: '0x111111',
signatureType: ''
},
{
owner: '0x2222222222222222222222222222222222222222',
submissionDate: '',
transactionHash: '',
confirmationType: '',
signature: '0x222222',
signatureType: ''
}
],
signatures: '0x111111222222'
}
const signedTx = await safeSdk.signTransaction(safeServiceTransaction)
chai.expect(safeServiceTransaction.confirmations?.length).to.be.eq(2)
chai.expect(signedTx.signatures.size).to.be.eq(3)
const signerAddress = await ethAdapter.getSignerAddress()
const signerSignature = signedTx.signatures.get(signerAddress!.toLowerCase())?.data
chai
.expect(signedTx.encodedSignatures())
.to.be.eq(safeServiceTransaction.signatures! + signerSignature!.slice(2))
})
})
})
1 change: 0 additions & 1 deletion packages/safe-ethers-adapters/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@
"sinon": "^14.0.0",
"sinon-chai": "^3.7.0",
"ts-generator": "^0.1.1",
"ts-node": "^10.9.1",
"typescript": "^4.9.3"
},
"lint-staged": {
Expand Down
1 change: 0 additions & 1 deletion packages/safe-ethers-lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
"eslint-plugin-prettier": "^4.2.1",
"hardhat": "^2.12.2",
"prettier": "^2.8.0",
"ts-node": "^10.9.1",
"typechain": "^8.1.1",
"typescript": "^4.9.3"
},
Expand Down
Loading