Skip to content

Commit

Permalink
Merge pull request #178 from maple-labs/sc-15947-feature-sdk-add-sign…
Browse files Browse the repository at this point in the history
…ed-transaction-broadcast-utils

sc-15947-feature-sdk-add-signed-transaction-broadcast-utils
  • Loading branch information
callum-hyland authored Mar 4, 2024
2 parents 015ab00 + 106c5f3 commit 0b53197
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 19 deletions.
53 changes: 48 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ A JavaScript SDK for interacting with Maple Protocol's smart contracts.
- [Additional Resources](#additional-resources)
- [Utils](#utils)
- [Generating Unsigned Transaction Data](#generating-unsigned-transaction-data)
- [Generating Signed Transaction Data](#generating-signed-transaction-data)
- [Broadcasting Signed Transactions](#broadcasting-signed-transactions)

## Getting Started

Expand Down Expand Up @@ -90,23 +92,23 @@ const method = await (await poolContract.deposit(depositAmount)).wait()

**Generating Unsigned Transaction Data**

This feature allows you to generate unsigned transaction data, facilitating the creation of transactions that can later be signed and sent to the network. This is particularly useful for offline transaction preparation or when keys are managed externally.
This utility allows you to generate unsigned transaction data, facilitating the creation of transactions that can later be signed and sent to the network. This is particularly useful for offline transaction preparation or when keys are managed externally.
Usage

```
import { utils } from '@maplelabs/maple-js'
const { txBytes, txInstance } = utils.generateTransactionData(parameters)
const { txBytes, txInstance } = utils.generateUnsignedTransactionData(parameters)
```

The `generateTransactionData` function supports creating unsigned transactions for specific actions, currently including `poolDeposit` and `poolQueueWithdrawal`. Depending on the action, parameters must be provided accordingly:
The `generateUnsignedTransactionData` function supports creating unsigned transactions for specific actions, currently including `poolDeposit` and `poolQueueWithdrawal`. Depending on the action, parameters must be provided accordingly:

- For `poolDeposit`, specify the deposit amount in assets.
- For `poolQueueWithdrawal`, specify the withdrawal amount in shares.

**_Parameters_**

All calls to `generateTransactionData` require the following parameters:
All calls to `generateUnsignedTransactionData` require the following parameters:

```
interface CommonInputs {
Expand Down Expand Up @@ -134,9 +136,50 @@ interface CommonInputs {
}
```

**Generating Signed Transaction Data**
This utility provides the functionality to combine unsigned transaction with a signature to create a signed transaction string. This is crucial for scenarios where transactions are prepared offline or in secure environments.
Usage

```
import { utils } from '@maplelabs/maple-js'
const signedTxData = utils.generateSignedTransactionData({
txBytes: 'unsignedTransactionBytes',
signature: 'signature'
})
```

**_Parameters_**

- `txBytes`: The serialized unsigned transaction data.
- ``signature`: The signature obtained from signing the transaction hash.

This function returns the serialized signed transaction data, ready for broadcasting to the Ethereum network.

**Broadcasting Signed Transactions**
This utilit allows you to broadcast a signed transaction to the Ethereum network. This step is the final stage in submitting transactions, where the transaction is sent to a node in the network for processing and inclusion in the blockchain.

Usage

```
import { utils } from '@maplelabs/maple-js'
const txReceipt = await utils.broadcastSignedTransaction(
'signedTransactionData',
'rpcUrl'
)
```

**_Parameters_**

- `signedTxData`: The serialized signed transaction data.
- `rpcUrl`: The URL of the Ethereum JSON-RPC endpoint to which you are broadcasting the transaction.

This function sends the signed transaction to the network and returns the transaction receipt once the transaction has been processed.

**_Example_**

An example usage of this feature, including parameter setup and function calls, can be found in the repository at `src/helpers/serialiseExampleUse`.
An example usage of these utilities, including parameter setup and function calls, can be found in the repository at `src/helpers/serialiseExampleUse`.

## Additional Resources

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@maplelabs/maple-js",
"version": "1.5.5",
"version": "1.5.6",
"description": "Maple SDK for JavaScript",
"author": "Maple Labs",
"license": "AGPL-3.0-only",
Expand Down
36 changes: 27 additions & 9 deletions src/helpers/serialiseExampleUse.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { utils } from 'ethers'
import { JsonRpcProvider } from '@ethersproject/providers'
import { Wallet } from '@ethersproject/wallet'
import { joinSignature, parseTransaction } from 'ethers/lib/utils'
import * as dotenv from 'dotenv'

import { UnsignedTransactionBundle, generateTransactionData } from './serialiseTransaction'
import {
UnsignedTransactionBundle,
generateUnsignedTransactionData,
generateSignedTransactionData,
broadcastSignedTransaction
} from './serialiseTransaction'

dotenv.config()

Expand All @@ -24,7 +29,7 @@ async function main() {
const walletWithProvider = wallet.connect(provider)

// 🚨 2) Serialize the transaction (unsigned) 🚨
const { txBytes, txInstance }: UnsignedTransactionBundle = await generateTransactionData({
const { txBytes }: UnsignedTransactionBundle = await generateUnsignedTransactionData({
provider,
walletAddress,
contractAddress: poolAddress,
Expand All @@ -45,26 +50,39 @@ async function main() {
// })

// 🚨 3) Sign the transaction 🚨
const deserializeTx = utils.parseTransaction(txBytes)
const { nonce, gasPrice, gasLimit, to, value, data, chainId } = deserializeTx
const deserializeTx = parseTransaction(txBytes)
const { nonce, gasPrice, gasLimit, to, value, data, chainId, accessList, type } = deserializeTx

if (!type) return

const transactionRequest = {
nonce,
// maxPriorityFeePerGas,
// maxFeePerGas,
gasPrice,
gasLimit,
to,
value: value.toHexString(),
data,
chainId
chainId,
accessList,
type
}

const signedTx = await walletWithProvider.signTransaction(transactionRequest)
const transactionParsed = parseTransaction(signedTx)
const { r, s, v } = transactionParsed

if (!r) return

const joinedSignature = joinSignature({ r, s, v })

console.log('✍🏼 :::', { signedTx })
const signedTxData = await generateSignedTransactionData({ txBytes, signature: joinedSignature })

// 🚨 4) Broadcast the transaction 🚨
const txResponse = await provider.sendTransaction(signedTx)
console.log('#️⃣ :::', { transactionHash: txResponse.hash })
const rpcUrl = process.env.RPC_URL as string
const txReceipt = await broadcastSignedTransaction(signedTxData, rpcUrl)
console.log({ txReceipt })
}

main()
44 changes: 42 additions & 2 deletions src/helpers/serialiseTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { BigNumber, BigNumberish, Contract, ContractInterface, UnsignedTransacti
import { serialize } from '@ethersproject/transactions'
import { Provider } from '@ethersproject/providers'
import { parseEther } from '@ethersproject/units'
import { parseTransaction, splitSignature } from 'ethers/lib/utils'
import { JsonRpcProvider } from '@ethersproject/providers'

import PoolV2PoolAbi from '../abis/PoolV2Pool.abi.json'

Expand Down Expand Up @@ -43,6 +45,11 @@ const createUnsignedTransactionBundle = async (
const gasPrice = await provider.getGasPrice()
const gasLimit = await estimateGasForFunction(contract, functionName, functionArgs, wallet)

// todo: get fee data so can use type 2: eip-1559
// const feeData = await provider.getFeeData()
// const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas || ZERO
// const maxFeePerGas = feeData.maxFeePerGas || ZERO

// Get current nonce
const nonce = await provider.getTransactionCount(wallet)

Expand All @@ -54,9 +61,14 @@ const createUnsignedTransactionBundle = async (
data: contract.interface.encodeFunctionData(functionName, functionArgs),
gasLimit: gasLimit.toHexString(),
gasPrice: gasPrice.toHexString(),
// maxPriorityFeePerGas: maxPriorityFeePerGas.toHexString(),
// maxFeePerGas: maxFeePerGas.toHexString(),
nonce,
value: parseEther('0').toHexString(),
chainId
chainId,
type: 1,

accessList: []
}

const txBytes = serialize(unsignedTx)
Expand Down Expand Up @@ -95,7 +107,7 @@ export interface PoolQueueWithdrawalInputs extends CommonInputs {

type TxParams = PoolDepositInputs | PoolQueueWithdrawalInputs

export const generateTransactionData = async (args: TxParams) => {
export const generateUnsignedTransactionData = async (args: TxParams) => {
const { provider, walletAddress, contractAddress, type } = args

const getTransactionParams = (): { abi: ContractInterface; params: any[]; functionName: string } => {
Expand All @@ -122,3 +134,31 @@ export const generateTransactionData = async (args: TxParams) => {

return await createUnsignedTransactionBundle(provider, walletAddress, contractAddress, abi, functionName, params)
}

interface GenerateSignedTransactionInput {
txBytes: string // Serialized unsigned transaction
signature: string // Hexidecimal string
}

export function generateSignedTransactionData({ txBytes, signature }: GenerateSignedTransactionInput) {
const decodedTx = parseTransaction(txBytes)
const splitSig = splitSignature(signature)

// Serialize the signed transaction
const serializedSignedTx = serialize(decodedTx, splitSig)
return serializedSignedTx
}

export async function broadcastSignedTransaction(signedTxData: string, rpcUrl: string) {
const provider = new JsonRpcProvider(rpcUrl)

const txResponse = await provider.sendTransaction(signedTxData)

console.log({ txResponse })

const txReceipt = await txResponse.wait()

console.log({ txReceipt })

return txReceipt
}
10 changes: 8 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ import sepoliaDevAddresses from './addresses/sepolia-dev'
import sepoliaProdAddresses from './addresses/sepolia-prod'

// utils
import { generateTransactionData } from 'helpers/serialiseTransaction'
import {
generateUnsignedTransactionData,
generateSignedTransactionData,
broadcastSignedTransaction
} from 'helpers/serialiseTransaction'

const debtLockerV2 = {
core: debtLockerV2Imports.DebtLockerV2Abi__factory,
Expand Down Expand Up @@ -359,7 +363,9 @@ interface ContractTypes {

// Utils
const utils = {
generateTransactionData
generateUnsignedTransactionData,
generateSignedTransactionData,
broadcastSignedTransaction
}

export {
Expand Down

0 comments on commit 0b53197

Please sign in to comment.