-
Notifications
You must be signed in to change notification settings - Fork 132
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Developer UI: Send transaction from an app (#133)
- Loading branch information
Showing
39 changed files
with
1,556 additions
and
450 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,4 +5,5 @@ dist | |
.vscode | ||
lerna-debug.log | ||
yalc.lock | ||
build | ||
build | ||
packages/safe-apps-developer-ui/src/types/contracts/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { ethers } from 'ethers'; | ||
|
||
const getEthBalance = async (provider: ethers.providers.BaseProvider, address: string): Promise<ethers.BigNumber> => { | ||
const balance = provider.getBalance(address); | ||
|
||
return balance; | ||
}; | ||
|
||
export { getEthBalance }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { ethers } from 'ethers'; | ||
import { getSafeContract } from './safeContracts'; | ||
|
||
const getSafeNonce = async (signer: ethers.providers.JsonRpcSigner, safeAddress: string): Promise<ethers.BigNumber> => { | ||
const safe = getSafeContract(safeAddress, signer); | ||
const nonce = await safe.nonce(); | ||
|
||
return nonce; | ||
}; | ||
|
||
export { getSafeNonce }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,54 @@ | ||
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json'; | ||
import MultiSendSol from '@gnosis.pm/safe-contracts/build/contracts/MultiSend.json'; | ||
import ProxyFactorySol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafeProxyFactory.json'; | ||
import FallbackHandlerSol from '@gnosis.pm/safe-contracts/build/contracts/FallbackManager.json'; | ||
import { JsonRpcSigner } from '@ethersproject/providers'; | ||
import { Contract, ContractFactory } from 'ethers'; | ||
import { GnosisSafe, GnosisSafeProxyFactory, MultiSend, MasterCopy, FallbackManager } from 'src/types/contracts'; | ||
|
||
const getProxyFactoryContract = (address: string, signer: JsonRpcSigner): Contract => | ||
new Contract(address, ProxyFactorySol.abi, signer); | ||
const getProxyFactoryContract = (address: string, signer: JsonRpcSigner): GnosisSafeProxyFactory => | ||
new Contract(address, ProxyFactorySol.abi, signer) as GnosisSafeProxyFactory; | ||
|
||
const getSafeContract = (address: string, signer: JsonRpcSigner): Contract => | ||
new Contract(address, GnosisSafeSol.abi, signer); | ||
const getSafeContract = (address: string, signer: JsonRpcSigner): GnosisSafe => | ||
new Contract(address, GnosisSafeSol.abi, signer) as GnosisSafe; | ||
|
||
const deployMasterCopy = async (signer: JsonRpcSigner): Promise<Contract> => { | ||
const getMultiSendContract = (address: string, signer: JsonRpcSigner): MultiSend => | ||
new Contract(address, MultiSendSol.abi, signer) as MultiSend; | ||
|
||
const deployMasterCopy = async (signer: JsonRpcSigner): Promise<MasterCopy> => { | ||
const factory = new ContractFactory(GnosisSafeSol.abi, GnosisSafeSol.bytecode, signer); | ||
const masterCopy = await factory.deploy(); | ||
|
||
return masterCopy; | ||
return masterCopy as MasterCopy; | ||
}; | ||
|
||
const deployProxyFactory = async (signer: JsonRpcSigner): Promise<Contract> => { | ||
const deployProxyFactory = async (signer: JsonRpcSigner): Promise<GnosisSafeProxyFactory> => { | ||
const factory = new ContractFactory(ProxyFactorySol.abi, ProxyFactorySol.bytecode, signer); | ||
const proxyFactory = await factory.deploy(); | ||
|
||
return proxyFactory; | ||
return proxyFactory as GnosisSafeProxyFactory; | ||
}; | ||
|
||
const deployFallbackHandler = async (signer: JsonRpcSigner): Promise<Contract> => { | ||
const deployFallbackHandler = async (signer: JsonRpcSigner): Promise<FallbackManager> => { | ||
const factory = new ContractFactory(FallbackHandlerSol.abi, FallbackHandlerSol.bytecode, signer); | ||
const fallbackHandler = await factory.deploy(); | ||
|
||
return fallbackHandler; | ||
return fallbackHandler as FallbackManager; | ||
}; | ||
|
||
export { deployProxyFactory, deployFallbackHandler, deployMasterCopy, getSafeContract, getProxyFactoryContract }; | ||
const deployMultiSend = async (signer: JsonRpcSigner): Promise<MultiSend> => { | ||
const factory = new ContractFactory(MultiSendSol.abi, MultiSendSol.bytecode, signer); | ||
const multiSend = await factory.deploy(); | ||
|
||
return multiSend as MultiSend; | ||
}; | ||
|
||
export { | ||
deployProxyFactory, | ||
deployFallbackHandler, | ||
deployMasterCopy, | ||
getSafeContract, | ||
getProxyFactoryContract, | ||
getMultiSendContract, | ||
deployMultiSend, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { EMPTY_DATA } from 'src/utils/strings'; | ||
|
||
const getPreValidatedSignature = (from: string, startStr: string = EMPTY_DATA): string => { | ||
return `${startStr}000000000000000000000000${from.replace( | ||
EMPTY_DATA, | ||
'', | ||
)}000000000000000000000000000000000000000000000000000000000000000001`; | ||
}; | ||
|
||
export { getPreValidatedSignature }; |
131 changes: 131 additions & 0 deletions
131
packages/safe-apps-developer-ui/src/api/transactions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import { ZERO_ADDRESS } from 'src/utils/strings'; | ||
import { SignedProposedTx } from './../types/transaction'; | ||
import { Transaction } from '@gnosis.pm/safe-apps-sdk'; | ||
import { ethers } from 'ethers'; | ||
import { getSafeContract, getMultiSendContract } from 'src/api/safeContracts'; | ||
import { ProposedTx, CreateTransactionArgs } from 'src/types/transaction'; | ||
import { getPreValidatedSignature } from './signatures'; | ||
import { getSafeNonce } from './safe'; | ||
|
||
const CALL = 0; | ||
const DELEGATE_CALL = 1; | ||
|
||
const executeTransaction = async ( | ||
signer: ethers.providers.JsonRpcSigner, | ||
safeAddress: string, | ||
{ baseGas, data, gasPrice, gasToken, operation, refundReceiver, safeTxGas, to, valueInWei, sigs }: SignedProposedTx, | ||
): Promise<ethers.ContractTransaction> => { | ||
const safeInstance = getSafeContract(safeAddress, signer); | ||
const execution = await safeInstance.execTransaction( | ||
to, | ||
valueInWei, | ||
data, | ||
operation, | ||
safeTxGas, | ||
baseGas, | ||
gasPrice, | ||
gasToken, | ||
refundReceiver, | ||
sigs, | ||
); | ||
|
||
return execution; | ||
}; | ||
|
||
const getTransactionHash = async ( | ||
signer: ethers.providers.JsonRpcSigner, | ||
safeAddress: string, | ||
{ | ||
baseGas, | ||
data, | ||
gasPrice, | ||
gasToken, | ||
nonce, | ||
operation, | ||
refundReceiver, | ||
safeTxGas, | ||
to, | ||
valueInWei, | ||
}: ProposedTx & { nonce: ethers.BigNumberish }, | ||
): Promise<string> => { | ||
const safeInstance = getSafeContract(safeAddress, signer); | ||
|
||
const txHash = await safeInstance.getTransactionHash( | ||
to, | ||
valueInWei, | ||
data, | ||
operation, | ||
safeTxGas, | ||
baseGas, | ||
gasPrice, | ||
gasToken, | ||
refundReceiver, | ||
nonce, | ||
); | ||
|
||
return txHash; | ||
}; | ||
|
||
const createTransaction = async ( | ||
signer: ethers.providers.JsonRpcSigner, | ||
safeAddress: string, | ||
sender: string, | ||
{ | ||
baseGas = 0, | ||
data, | ||
gasPrice = 0, | ||
gasToken = ZERO_ADDRESS, | ||
operation = CALL, | ||
refundReceiver = ZERO_ADDRESS, | ||
safeTxGas = 0, | ||
to, | ||
valueInWei, | ||
}: CreateTransactionArgs, | ||
): Promise<{ safeTxHash: string; tx: ethers.ContractTransaction }> => { | ||
const senderSignature = getPreValidatedSignature(sender); | ||
const safeNonce = await getSafeNonce(signer, safeAddress); | ||
|
||
const tx = { | ||
baseGas, | ||
data, | ||
gasPrice, | ||
gasToken, | ||
operation, | ||
refundReceiver, | ||
safeTxGas, | ||
nonce: safeNonce, | ||
to, | ||
valueInWei, | ||
sigs: senderSignature, | ||
}; | ||
|
||
const safeTxHash = await getTransactionHash(signer, safeAddress, tx); | ||
const executedTx = await executeTransaction(signer, safeAddress, tx); | ||
|
||
return { safeTxHash, tx: executedTx }; | ||
}; | ||
|
||
const encodeMultiSendCall = ( | ||
signer: ethers.providers.JsonRpcSigner, | ||
multiSendAddress: string, | ||
txs: Transaction[], | ||
): string => { | ||
const multiSend = getMultiSendContract(multiSendAddress, signer); | ||
|
||
const joinedTxs = txs | ||
.map((tx) => { | ||
const data = ethers.utils.arrayify(tx.data); | ||
const encoded = ethers.utils.solidityPack( | ||
['uint8', 'address', 'uint256', 'uint256', 'bytes'], | ||
[0, tx.to, tx.value, data.length, data], | ||
); | ||
return encoded.slice(2); | ||
}) | ||
.join(''); | ||
|
||
const encodedMultiSendCallData = multiSend.interface.encodeFunctionData('multiSend', [`0x${joinedTxs}`]); | ||
|
||
return encodedMultiSendCallData; | ||
}; | ||
|
||
export { CALL, DELEGATE_CALL, encodeMultiSendCall, getTransactionHash, createTransaction }; |
11 changes: 11 additions & 0 deletions
11
packages/safe-apps-developer-ui/src/assets/icons/icon-arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions
10
packages/safe-apps-developer-ui/src/assets/icons/icon-code.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions
30
packages/safe-apps-developer-ui/src/components/BalanceBox.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import React from 'react'; | ||
import styled from 'styled-components'; | ||
import { secondaryBackground, xs } from 'src/styles/variables'; | ||
|
||
const Box = styled.p` | ||
background: ${secondaryBackground}; | ||
padding: ${xs}; | ||
border-radius: 5px; | ||
width: fit-content; | ||
span { | ||
font-weight: bold; | ||
} | ||
`; | ||
|
||
type Props = { | ||
balance: string; | ||
symbol?: string; | ||
}; | ||
|
||
const BalanceBox = ({ balance, symbol = 'ETH' }: Props): React.ReactElement => ( | ||
<Box> | ||
Balance:{' '} | ||
<span> | ||
{balance} {symbol} | ||
</span> | ||
</Box> | ||
); | ||
|
||
export { BalanceBox }; |
Oops, something went wrong.