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

feat(sdk-starter-kit): Add owner management methods #949

Merged
merged 3 commits into from
Aug 21, 2024
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
8 changes: 8 additions & 0 deletions packages/protocol-kit/src/Safe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,14 @@ class Safe {
return this.#safeProvider
}

/**
* Returns the OwnerManager
* @returns {OwnerManager} The current OwnerManager
*/
getOwnerManager(): OwnerManager {
return this.#ownerManager
}

/**
* Returns the address of the MultiSend contract.
*
Expand Down
162 changes: 162 additions & 0 deletions packages/sdk-starter-kit/src/BaseClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import Safe, {
AddOwnerTxParams,
RemoveOwnerTxParams,
SwapOwnerTxParams
} from '@safe-global/protocol-kit'
import SafeApiKit from '@safe-global/api-kit'
import { TransactionBase } from '@safe-global/safe-core-sdk-types'

import { ChangeThresholdTxParams } from './types'

export class BaseClient {
protocolKit: Safe
apiKit: SafeApiKit

constructor(protocolKit: Safe, apiKit: SafeApiKit) {
this.protocolKit = protocolKit
this.apiKit = apiKit
}

/**
* Returns the Safe address.
*
* @returns {string} The Safe address
*/
async getAddress(): Promise<string> {
return this.protocolKit.getAddress()
}

/**
* Checks if the current Safe is deployed.
*
* @returns {boolean} if the Safe contract is deployed
*/
async isDeployed(): Promise<boolean> {
return this.protocolKit.isSafeDeployed()
}

/**
* Checks if a specific address is an owner of the current Safe.
*
* @param {string} ownerAddress - The account address
* @returns {boolean} TRUE if the account is an owner
*/
async isOwner(ownerAddress: string): Promise<boolean> {
return this.protocolKit.isOwner(ownerAddress)
}

/**
* Returns the list of Safe owner accounts.
*
* @returns The list of owners
*/
async getOwners(): Promise<string[]> {
return this.protocolKit.getOwners()
}

/**
* Returns the Safe threshold.
*
* @returns {number} The Safe threshold
*/
async getThreshold(): Promise<number> {
return this.protocolKit.getThreshold()
}

/**
* Returns the Safe nonce.
*
* @returns {number} The Safe nonce
*/
async getNonce(): Promise<number> {
return this.protocolKit.getNonce()
}

/**
* Returns a list of owners who have approved a specific Safe transaction.
*
* @param {string} txHash - The Safe transaction hash
* @returns {string[]} The list of owners
*/
async getOwnersWhoApprovedTx(txHash: string): Promise<string[]> {
return this.protocolKit.getOwnersWhoApprovedTx(txHash)
}

/**
* Encodes the data for adding a new owner to the Safe.
*
* @param {AddOwnerTxParams} params - The parameters for adding a new owner
* @param {string} params.ownerAddress - The address of the owner to add
* @param {number} params.threshold - The threshold of the Safe
* @returns {TransactionBase} The encoded data
*/
async createAddOwnerTransaction({
ownerAddress,
threshold
}: AddOwnerTxParams): Promise<TransactionBase> {
const ownerManager = this.protocolKit.getOwnerManager()

return this.#buildTransaction(
await ownerManager.encodeAddOwnerWithThresholdData(ownerAddress, threshold)
)
}

/**
* Encodes the data for removing an owner from the Safe.
*
* @param {RemoveOwnerTxParams} params - The parameters for removing an owner
* @param {string} params.ownerAddress - The address of the owner to remove
* @param {number} params.threshold - The threshold of the Safe
* @returns {TransactionBase} The encoded data
*/
async createRemoveOwnerTransaction({
ownerAddress,
threshold
}: RemoveOwnerTxParams): Promise<TransactionBase> {
const ownerManager = this.protocolKit.getOwnerManager()

return this.#buildTransaction(await ownerManager.encodeRemoveOwnerData(ownerAddress, threshold))
}

/**
* Encodes the data for swapping an owner in the Safe.
*
* @param {SwapOwnerTxParams} params - The parameters for swapping an owner
* @param {string} params.oldOwnerAddress - The address of the old owner
* @param {string} params.newOwnerAddress - The address of the new owner
* @returns {TransactionBase} The encoded data
*/
async createSwapOwnerTransaction({
oldOwnerAddress,
newOwnerAddress
}: SwapOwnerTxParams): Promise<TransactionBase> {
const ownerManager = this.protocolKit.getOwnerManager()

return this.#buildTransaction(
await ownerManager.encodeSwapOwnerData(oldOwnerAddress, newOwnerAddress)
)
}

/**
* Encodes the data for changing the Safe threshold.
*
* @param {ChangeThresholdTxParams} params - The parameters for changing the Safe threshold
* @param {number} params.threshold - The new threshold
* @returns {TransactionBase} The encoded data
*/
async createChangeThresholdTransaction({
threshold
}: ChangeThresholdTxParams): Promise<TransactionBase> {
const ownerManager = this.protocolKit.getOwnerManager()

return this.#buildTransaction(await ownerManager.encodeChangeThresholdData(threshold))
}

async #buildTransaction(encodedData: string): Promise<TransactionBase> {
return {
to: await this.protocolKit.getAddress(),
value: '0',
data: encodedData
}
}
}
10 changes: 4 additions & 6 deletions packages/sdk-starter-kit/src/SafeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
SendTransactionProps
} from '@safe-global/sdk-starter-kit/types'

import { BaseClient } from './BaseClient'

/**
* @class
* This class provides the core functionality to create, sign and execute transactions.
Expand All @@ -31,13 +33,9 @@ import {
* const { transactions } = await safeClient.send(...)
* await safeClient.confirm(transactions?.safeTxHash)
*/
export class SafeClient {
protocolKit: Safe
apiKit: SafeApiKit

export class SafeClient extends BaseClient {
constructor(protocolKit: Safe, apiKit: SafeApiKit) {
this.protocolKit = protocolKit
this.apiKit = apiKit
super(protocolKit, apiKit)
}

/**
Expand Down
4 changes: 4 additions & 0 deletions packages/sdk-starter-kit/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,7 @@ export type SafeClientResult = {
ethereumTxHash?: string
}
}

export type ChangeThresholdTxParams = {
threshold: number
}
3 changes: 2 additions & 1 deletion playground/config/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ const playgroundStarterKitPaths = {
'send-transactions': 'sdk-starter-kit/send-transactions',
'send-on-chain-message': 'sdk-starter-kit/send-on-chain-message',
'send-off-chain-message': 'sdk-starter-kit/send-off-chain-message',
'send-safe-operation': 'sdk-starter-kit/send-safe-operation'
'send-safe-operation': 'sdk-starter-kit/send-safe-operation',
'owner-management': 'sdk-starter-kit/owner-management'
}

const path =
Expand Down
76 changes: 76 additions & 0 deletions playground/sdk-starter-kit/owner-management.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { createSafeClient } from '@safe-global/sdk-starter-kit'

const OWNER_1_PRIVATE_KEY = ''
const OWNER_2_PRIVATE_KEY = ''
const OWNER_2_ADDRESS = ''

const RPC_URL = 'https://sepolia.gateway.tenderly.co'
const SAFE_ADDRESS = ''

async function addOwner() {
const safeClient = await createSafeClient({
provider: RPC_URL,
signer: OWNER_1_PRIVATE_KEY,
safeAddress: SAFE_ADDRESS
})

const transaction = await safeClient.createAddOwnerTransaction({
ownerAddress: OWNER_2_ADDRESS,
threshold: 2
})

const transactionResult = await safeClient.send({ transactions: [transaction] })

console.log('Add Owner Transaction Result', transactionResult)
}

async function removeOwner() {
const safeClient1 = await createSafeClient({
provider: RPC_URL,
signer: OWNER_1_PRIVATE_KEY,
safeAddress: SAFE_ADDRESS
})

const safeClient2 = await createSafeClient({
provider: RPC_URL,
signer: OWNER_2_PRIVATE_KEY,
safeAddress: SAFE_ADDRESS
})

const transaction = await safeClient1.createRemoveOwnerTransaction({
ownerAddress: OWNER_2_ADDRESS,
threshold: 1
})
const sendResult = await safeClient1.send({ transactions: [transaction] })

const transactionResult = await safeClient2.confirm({
safeTxHash: sendResult.transactions?.safeTxHash || ''
})
console.log('Remove Owner Transaction Result', transactionResult)
}

async function safeInfo() {
const safeClient = await createSafeClient({
provider: RPC_URL,
signer: OWNER_1_PRIVATE_KEY,
safeAddress: SAFE_ADDRESS
})

console.log('Safe Address', await safeClient.protocolKit.getAddress())
console.log('Owners', await safeClient.getOwners())
console.log('Threshold', await safeClient.getThreshold())
console.log('Nonce', await safeClient.getNonce())
}

async function main() {
await safeInfo()
await addOwner()

console.log('Waiting for transaction to be indexed ...')
setTimeout(async () => {
await safeInfo()
await removeOwner()
}, 10000)
}

main()
Loading