Skip to content

Commit

Permalink
Merge pull request #51 from helix-bridge/xiaoch05-upgrade-safe
Browse files Browse the repository at this point in the history
upgrade safe sdk
  • Loading branch information
xiaoch05 authored Dec 18, 2024
2 parents f909a54 + 88b45ce commit 6966414
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 2,047 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
"@nestjs/core": "^9.0.0",
"@nestjs/platform-express": "^9.0.0",
"@nestjs/schedule": "^1.1.0",
"@safe-global/api-kit": "^2.0.0",
"@safe-global/protocol-kit": "^2.0.0",
"@safe-global/api-kit": "^2.5.5",
"@safe-global/protocol-kit": "^5.1.0",
"@safe-global/safe-core-sdk-types": "^5.0.2",
"axios": "^1.2.3",
"dids": "^5.0.2",
Expand Down
4 changes: 4 additions & 0 deletions src/base/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ export class EthereumProvider {
return this.provider;
}

get url(): string {
return this.urls[this.urlIndex % this.urls.length];
}

@rpcCallIfError
async currentBlocknumber() {
return await this.provider.getBlockNumber();
Expand Down
53 changes: 0 additions & 53 deletions src/base/safe-service/ceramic.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,57 +93,4 @@ export class CeramicService extends SafeService {

return Promise.resolve();
}

async getTransactionConfirmations(
safeTxHash: string
): Promise<SafeMultisigConfirmationResponse[]> {
if (!this.composeClient) {
await this.init();
}

try {
const confirmationsIndex: ConfirmationIndexData = (await this
.composeClient.executeQuery(`
query ConfirmationIndex {
confirmationIndex(
first: 99
filters: { where: { transactionHash: { equalTo: "${safeTxHash}" } } }
) {
edges {
node {
owner
id
signature
signatureType
submissionDate
transactionHash
confirmationType
}
}
}
}
`)) as ConfirmationIndexData;
const confirmations = confirmationsIndex.data.confirmationIndex.edges
.map((edge) => edge.node)
.filter((confirmation) => {
const { signature } = confirmation;
let signatureV: number = parseInt(signature.slice(-2), 16);
// must be signed by with prefix, otherwise, we can't verify this message
if (signatureV !== 31 && signatureV !== 32) {
return false;
}
signatureV -= 4;
const normalizedSignature =
signature.slice(0, -2) + signatureV.toString(16);
return (
ethers
.verifyMessage(ethers.getBytes(safeTxHash), normalizedSignature)
.toLowerCase() === confirmation.owner.toLowerCase()
);
});
return confirmations as SafeMultisigConfirmationResponse[];
} catch (error) {
// console.error('Error fetching transaction:', error);
}
}
}
3 changes: 0 additions & 3 deletions src/base/safe-service/safe.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,5 @@ export abstract class SafeService {
this.name = name;
this.url = url;
}
abstract getTransactionConfirmations(
safeTxHash: string
): Promise<SafeMultisigConfirmationResponse[]>;
abstract proposeTransaction(prop: ProposeTransactionProps): Promise<void>;
}
7 changes: 0 additions & 7 deletions src/base/safe-service/safeglobal.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,4 @@ export class SafeGlobalService extends SafeService {
async proposeTransaction(props: ProposeTransactionProps): Promise<void> {
await this.safeService.proposeTransaction(props);
}

async getTransactionConfirmations(
safeTxHash: string
): Promise<SafeMultisigConfirmationResponse[]> {
const transaction = await this.safeService.getTransaction(safeTxHash);
return transaction?.confirmations;
}
}
18 changes: 0 additions & 18 deletions src/base/safe-service/single.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,4 @@ export class SingleService extends SafeService {
async proposeTransaction(props: ProposeTransactionProps): Promise<void> {
this.props = props;
}

async getTransactionConfirmations(
safeTxHash: string
): Promise<SafeMultisigConfirmationResponse[]> {
if (safeTxHash !== this.props?.safeTxHash) {
return [];
}
return [
{
owner: this.props.senderAddress,
signature: this.props.senderSignature,
signatureType: "ECDSA",
transactionHash: safeTxHash,
submissionDate: new Date().toISOString(),
confirmationType: "approve",
},
];
}
}
163 changes: 30 additions & 133 deletions src/base/safewallet.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,21 @@
import { Logger } from "@nestjs/common";
import {
MetaTransactionData,
SafeMultisigConfirmationResponse,
SafeTransaction,
} from "@safe-global/safe-core-sdk-types";
import Safe, { EthersAdapter } from "@safe-global/protocol-kit";
import SafeApiKit from "@safe-global/api-kit";
import Safe, { buildSignatureBytes, EthSafeSignature } from "@safe-global/protocol-kit";
import { ethers, Wallet, HDNodeWallet } from "ethers";
import { SafeService } from "./safe-service/safe.service";
import { EthereumConnectedWallet } from "./wallet";

export interface TransactionPropose {
to: string;
value: bigint;
readyExecute: boolean;
safeTxHash: string;
txData: string;
operation: number;
signatures: string | null;
}

export interface SignatureInfo {
size: number;
signatures: string;
selfSigned: boolean;
signedTransaction: SafeTransaction;
}

export class SafeWallet {
public address: string;
public wallet: EthereumConnectedWallet;
public owners: string[];
public threshold: number;
private safeSdk: Safe;
private safeService: SafeService;
Expand All @@ -49,149 +36,59 @@ export class SafeWallet {
}

async connect(chainId: bigint) {
const ethAdapter = new EthersAdapter({
ethers,
signerOrProvider: this.wallet.SignerOrProvider,
});

this.safeSdk = await Safe.create({
ethAdapter: ethAdapter,
this.safeSdk = await Safe.init({
provider: this.wallet.url(),
signer: this.wallet.privateKey,
safeAddress: this.address,
});
this.owners = (await this.safeSdk.getOwners()).map((o) => o.toLowerCase());
this.threshold = await this.safeSdk.getThreshold();
}

private concatSignatures(
confirmations: SafeMultisigConfirmationResponse[]
): SignatureInfo {
// must sort by address
confirmations.sort(
(
left: SafeMultisigConfirmationResponse,
right: SafeMultisigConfirmationResponse
) => {
const leftAddress = left.owner.toLowerCase();
const rightAddress = right.owner.toLowerCase();
if (leftAddress < rightAddress) {
return -1;
} else {
return 1;
}
}
);
var signatures = "0x";
const uniqueOwners = [];
for (const confirmation of confirmations) {
signatures += confirmation.signature.substring(2);
if (
uniqueOwners.includes(confirmation.owner.toLowerCase()) ||
!this.owners.includes(confirmation.owner.toLowerCase())
) {
continue;
}
uniqueOwners.push(confirmation.owner.toLowerCase());
}
return {
size: uniqueOwners.length,
signatures: signatures,
selfSigned: uniqueOwners.includes(
this.wallet.wallet.address.toLowerCase()
),
};
}

async proposeTransaction(
transactions: MetaTransactionData[],
isExecuter: boolean,
chainId: bigint
): Promise<TransactionPropose | null> {
this.safeSdk ?? (await this.connect(chainId));
const tx = await this.safeSdk.createTransaction({ transactions });
const safeTxHash = await this.safeSdk.getTransactionHash(tx);

const propose = {
safeTxHash: safeTxHash,
txData: tx.data.data,
to: tx.data.to,
value: BigInt(tx.data.value),
operation: tx.data.operation,
};
const txHash = await this.safeSdk.getTransactionHash(tx);

if (this.threshold === 1) {
if (isExecuter) {
const signature = await this.safeSdk.signTransactionHash(safeTxHash);
const signedTransaction = await this.safeSdk.signTransaction(tx);
return {
...propose,
readyExecute: true,
signatures: signature.data,
signedTransaction: signedTransaction,
};
} else {
return null;
}
} else {
let confirmations: SafeMultisigConfirmationResponse[];
try {
confirmations = await this.safeService.getTransactionConfirmations(
safeTxHash
);
} catch {
confirmations = [];
}
var signatureInfo: SignatureInfo = this.concatSignatures(confirmations);
if (signatureInfo.selfSigned) {
return {
...propose,
readyExecute: signatureInfo.size >= this.threshold,
signatures: signatureInfo.signatures,
};
} else {
const signature = await this.safeSdk.signTransactionHash(safeTxHash);
if (!isExecuter) {
if (signatureInfo.size < this.threshold) {
const proposeTransactionProps = {
safeAddress: this.address,
safeTransactionData: tx.data,
safeTxHash,
senderAddress: this.wallet.wallet.address,
senderSignature: signature.data,
};
try {
await this.safeService.proposeTransaction(
proposeTransactionProps
);
this.logger.log(
`finish to propose transaction ${safeTxHash} using ${this.safeService.name} on chain ${chainId}`
);
} catch (err) {
this.logger.warn(
`propose transaction ${safeTxHash} using ${this.safeService.name} on chain ${chainId} failed, err ${err}`
);
}
}
return {
...propose,
readyExecute: signatureInfo.size >= this.threshold,
signatures: signatureInfo.signatures,
};
} else {
const readyExecute = signatureInfo.size + 1 >= this.threshold;
const signedTransaction = await this.safeSdk.signTransaction(tx);
const readyExecute = signedTransaction.signatures.size >= this.threshold;
if (signedTransaction.signatures.size < this.threshold) {
try {
const signature = tx.getSignature(this.wallet.address) as EthSafeSignature;
await this.safeService.proposeTransaction({
safeAddress: this.address,
safeTransactionData: tx.data,
safeTxHash: txHash,
senderAddress: this.wallet.address,
senderSignature: buildSignatureBytes([signature])
});
this.logger.log(
`${
readyExecute ? "ready" : "waiting"
} to execute, tx ${safeTxHash} on chain ${chainId}`
`finish to propose transaction ${txHash} using ${this.safeService.name} on chain ${chainId}`
);
} catch (err) {
this.logger.warn(
`propose transaction ${txHash} using ${this.safeService.name} on chain ${chainId} failed, err ${err}`
);
const newSignatures =
signatureInfo.size >= this.threshold
? signatureInfo.signatures
: signatureInfo.signatures + signature.data.substring(2);
return {
...propose,
readyExecute: readyExecute,
signatures: newSignatures,
};
}
}
return {
readyExecute: readyExecute,
signedTransaction: signedTransaction
};
}
}
}
8 changes: 8 additions & 0 deletions src/base/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,16 @@ export class EthereumConnectedWallet {
public wallet: Wallet | HDNodeWallet;
public onProviderUpdatedHandlers: (() => void)[] = [];
public tryNextUrl: () => void;
public url: () => string;

constructor(privateKey: string, provider: EthereumProvider) {
this.wallet = new Wallet(privateKey, provider.provider);
this.tryNextUrl = () => {
provider.tryNextUrl();
};
this.url = () => {
return provider.url;
};
provider.registerUrlUpdateHandler(() => {
this.wallet = new Wallet(privateKey, provider.provider);
for (const handler of this.onProviderUpdatedHandlers) {
Expand All @@ -48,6 +52,10 @@ export class EthereumConnectedWallet {
return this.wallet.address;
}

get privateKey() {
return this.wallet.privateKey;
}

get SignerOrProvider(): Wallet | HDNodeWallet | ethers.Provider {
return this.wallet;
}
Expand Down
Loading

0 comments on commit 6966414

Please sign in to comment.