Skip to content

Commit

Permalink
feat(sdk-starter-kit): Add owner management methods (#949)
Browse files Browse the repository at this point in the history
  • Loading branch information
yagopv authored Aug 21, 2024
1 parent 61284fb commit cf6f033
Show file tree
Hide file tree
Showing 6 changed files with 256 additions and 7 deletions.
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()

0 comments on commit cf6f033

Please sign in to comment.