From c0058521f16a375f7f22cd090b542b8006ce6d4e Mon Sep 17 00:00:00 2001 From: Daniel Somoza Date: Sun, 24 Nov 2024 21:50:57 +0100 Subject: [PATCH 01/12] append the on chain trackId to the transaction data field --- packages/protocol-kit/src/Safe.ts | 54 ++++++-- packages/protocol-kit/src/types/safeConfig.ts | 4 + .../utils/on-chain-tracking/formatTrackId.ts | 15 +++ .../tests/e2e/onChainTrackId.test.ts | 118 ++++++++++++++++++ 4 files changed, 184 insertions(+), 7 deletions(-) create mode 100644 packages/protocol-kit/src/utils/on-chain-tracking/formatTrackId.ts create mode 100644 packages/protocol-kit/tests/e2e/onChainTrackId.test.ts diff --git a/packages/protocol-kit/src/Safe.ts b/packages/protocol-kit/src/Safe.ts index 3b9dd9859..4b99838b7 100644 --- a/packages/protocol-kit/src/Safe.ts +++ b/packages/protocol-kit/src/Safe.ts @@ -20,6 +20,7 @@ import { getChainSpecificDefaultSaltNonce, getPredictedSafeAddressInitCode, predictSafeAddress, + toTxResult, validateSafeAccountConfig, validateSafeDeploymentConfig } from './contracts/utils' @@ -81,9 +82,10 @@ import SafeMessage from './utils/messages/SafeMessage' import semverSatisfies from 'semver/functions/satisfies' import SafeProvider from './SafeProvider' import { asHash, asHex } from './utils/types' -import { Hash, Hex } from 'viem' +import { Hash, Hex, SendTransactionParameters } from 'viem' import getPasskeyOwnerAddress from './utils/passkeys/getPasskeyOwnerAddress' import createPasskeyDeploymentTransaction from './utils/passkeys/createPasskeyDeploymentTransaction' +import formatTrackId from './utils/on-chain-tracking/formatTrackId' const EQ_OR_GT_1_4_1 = '>=1.4.1' const EQ_OR_GT_1_3_0 = '>=1.3.0' @@ -100,6 +102,9 @@ class Safe { #MAGIC_VALUE = '0x1626ba7e' #MAGIC_VALUE_BYTES = '0x20c13b0b' + // On-chain Analitics + #trackId: string = '' + /** * Creates an instance of the Safe Core SDK. * @param config - Ethers Safe configuration @@ -124,7 +129,11 @@ class Safe { * @throws "MultiSendCallOnly contract is not deployed on the current network" */ async #initializeProtocolKit(config: SafeConfig) { - const { provider, signer, isL1SafeSingleton, contractNetworks } = config + const { provider, signer, isL1SafeSingleton, contractNetworks, trackId } = config + + if (trackId) { + this.#trackId = formatTrackId(trackId) + } this.#safeProvider = await SafeProvider.init({ provider, @@ -1338,6 +1347,32 @@ class Safe { const signerAddress = await this.#safeProvider.getSignerAddress() + if (this.#trackId) { + const encodedTransaction = await this.getEncodedTransaction(signedSafeTransaction) + + const transaction = { + to: await this.getAddress(), + value: 0n, + data: encodedTransaction + this.#trackId + } + + const signer = await this.#safeProvider.getExternalSigner() + + if (!signer) { + throw new Error('A signer must be set') + } + + const hash = await signer.sendTransaction({ + ...transaction, + account: signerAddress, + ...options + } as SendTransactionParameters) + + const provider = this.#safeProvider.getExternalProvider() + + return toTxResult(provider, hash, options) + } + const txResponse = await this.#contractManager.safeContract.execTransaction( signedSafeTransaction, { @@ -1552,11 +1587,12 @@ class Safe { to: safeProxyFactoryContract.getAddress(), value: '0', // we use the createProxyWithNonce method to create the Safe in a deterministic address, see: https://github.com/safe-global/safe-contracts/blob/main/contracts/proxies/SafeProxyFactory.sol#L52 - data: safeProxyFactoryContract.encode('createProxyWithNonce', [ - asHex(safeSingletonContract.getAddress()), - asHex(initializer), // call to the setup method to set the threshold & owners of the new Safe - BigInt(saltNonce) - ]) + data: + safeProxyFactoryContract.encode('createProxyWithNonce', [ + asHex(safeSingletonContract.getAddress()), + asHex(initializer), // call to the setup method to set the threshold & owners of the new Safe + BigInt(saltNonce) + ]) + this.#trackId } return safeDeployTransactionData @@ -1698,6 +1734,10 @@ class Safe { }): ContractInfo | undefined => { return getContractInfo(contractAddress) } + + getTrackId(): string { + return this.#trackId + } } export default Safe diff --git a/packages/protocol-kit/src/types/safeConfig.ts b/packages/protocol-kit/src/types/safeConfig.ts index 11d606ab9..9fa1b16ac 100644 --- a/packages/protocol-kit/src/types/safeConfig.ts +++ b/packages/protocol-kit/src/types/safeConfig.ts @@ -48,6 +48,8 @@ export type SafeConfigProps = { isL1SafeSingleton?: boolean /** contractNetworks - Contract network configuration */ contractNetworks?: ContractNetworksConfig + // on-chain analitics + trackId?: string } export type SafeConfigWithSafeAddress = SafeConfigProps & SafeConfigWithSafeAddressProps @@ -75,6 +77,8 @@ type ConnectSafeConfigProps = { isL1SafeSingleton?: boolean /** contractNetworks - Contract network configuration */ contractNetworks?: ContractNetworksConfig + // on-chain analitics + trackId?: string } export type ConnectSafeConfigWithSafeAddress = ConnectSafeConfigProps & diff --git a/packages/protocol-kit/src/utils/on-chain-tracking/formatTrackId.ts b/packages/protocol-kit/src/utils/on-chain-tracking/formatTrackId.ts new file mode 100644 index 000000000..7d6ee67ca --- /dev/null +++ b/packages/protocol-kit/src/utils/on-chain-tracking/formatTrackId.ts @@ -0,0 +1,15 @@ +import { keccak256, toHex } from 'viem' + +/** + * Converts a trackId into a hash for tracking Safe transactions and deployments on a blockchain. + * This function converts the provided trackId to hexadecimal format, hashes it using keccak256, + * and returns the first 40 characters of the hash (excluding the '0x' prefix). + * + * @param {string} trackId - The identifier provided by the user to be formatted into a hash. + * @returns {string} - A 40-character hexadecimal string representing the hash of the trackId. + */ +function formatTrackId(trackId: string): string { + return keccak256(toHex(trackId)).substring(2, 42) // only first 40 chars from the hash (excluding the '0x' prefix) +} + +export default formatTrackId diff --git a/packages/protocol-kit/tests/e2e/onChainTrackId.test.ts b/packages/protocol-kit/tests/e2e/onChainTrackId.test.ts new file mode 100644 index 000000000..73f416bf3 --- /dev/null +++ b/packages/protocol-kit/tests/e2e/onChainTrackId.test.ts @@ -0,0 +1,118 @@ +import { setupTests, safeVersionDeployed } from '@safe-global/testing-kit' +import Safe, { PredictedSafeProps, SafeAccountConfig } from '@safe-global/protocol-kit/index' +import chai from 'chai' +import chaiAsPromised from 'chai-as-promised' + +import { getEip1193Provider } from './utils/setupProvider' +import { waitSafeTxReceipt } from './utils/transactions' + +chai.use(chaiAsPromised) + +describe('On-chain analytics', () => { + const provider = getEip1193Provider() + + describe('getTrackId method', () => { + it('should return the correctly formatted track id when provided', async () => { + const trackId = 'test-track-id' + const { safe, contractNetworks } = await setupTests() + const safeAddress = safe.address + + const protocolKit = await Safe.init({ + provider, + safeAddress, + contractNetworks, + trackId + }) + + const formattedTrackId = '7ba67a7e86c9fad9f51790e7e60307f882b9c492' + + chai.expect(formattedTrackId).to.equals(protocolKit.getTrackId()) + }) + + it('should return an empty string when no track id is provided', async () => { + const { safe, contractNetworks } = await setupTests() + const safeAddress = safe.address + + const protocolKit = await Safe.init({ + provider, + safeAddress, + contractNetworks + }) + + chai.expect(protocolKit.getTrackId()).to.empty + }) + }) + + describe('Tracking Safe Deployment on Chain via the transaction data field', () => { + it('should append the formatted trackId to the deployment transaction data field', async () => { + const trackId = 'test-track-id' + + const { accounts, contractNetworks } = await setupTests() + const [account1, account2] = accounts + const owners = [account1.address, account2.address] + const threshold = 1 + const safeAccountConfig: SafeAccountConfig = { owners, threshold } + const predictedSafe: PredictedSafeProps = { + safeAccountConfig, + safeDeploymentConfig: { + safeVersion: safeVersionDeployed + } + } + + const protocolKit = await Safe.init({ + provider, + predictedSafe, + contractNetworks, + trackId + }) + + const deploymentTransaction = await protocolKit.createSafeDeploymentTransaction() + + const formattedTrackId = '7ba67a7e86c9fad9f51790e7e60307f882b9c492' + + chai.expect(formattedTrackId).to.equals(protocolKit.getTrackId()) + chai.expect(deploymentTransaction.data.endsWith(formattedTrackId)).to.be.true + }) + }) + + describe('Tracking Safe transactions on Chain via the transaction data field', () => { + it('should append the formatted trackId to the exec transaction data field', async () => { + const trackId = 'test-track-id' + const { safe, contractNetworks } = await setupTests() + const safeAddress = safe.address + + const protocolKit = await Safe.init({ + provider, + safeAddress, + contractNetworks, + trackId + }) + + const testTransaction = { + to: safeAddress, + value: '0', + data: '0x' + } + + const safeTransaction = await protocolKit.createTransaction({ + transactions: [testTransaction] + }) + + const isValidTransaction = await protocolKit.isValidTransaction(safeTransaction) + chai.expect(isValidTransaction).to.be.eq(true) + + const transactionResponse = await protocolKit.executeTransaction(safeTransaction) + + await waitSafeTxReceipt(transactionResponse) + + const transaction = await protocolKit + .getSafeProvider() + .getTransaction(transactionResponse.hash) + + const formattedTrackId = '7ba67a7e86c9fad9f51790e7e60307f882b9c492' + + chai.expect(formattedTrackId).to.equals(protocolKit.getTrackId()) + chai.expect(transaction.input.endsWith(formattedTrackId)).to.be.true + }) + }) +}) From c22aa9521f0a56cf299319c6f711b189eccc3d50 Mon Sep 17 00:00:00 2001 From: Daniel Somoza Date: Thu, 28 Nov 2024 14:03:18 +0100 Subject: [PATCH 02/12] add new onchain identifier format to track transactions --- packages/protocol-kit/src/Safe.ts | 64 +++++++++++++------ packages/protocol-kit/src/index.ts | 2 + packages/protocol-kit/src/types/safeConfig.ts | 9 ++- .../generateOnChainIdentifier.ts | 41 ++++++++++++ .../src/packs/safe-4337/Safe4337Pack.ts | 42 ++++++++++-- .../relay-kit/src/packs/safe-4337/types.ts | 10 ++- 6 files changed, 142 insertions(+), 26 deletions(-) create mode 100644 packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts diff --git a/packages/protocol-kit/src/Safe.ts b/packages/protocol-kit/src/Safe.ts index 4b99838b7..636daa84b 100644 --- a/packages/protocol-kit/src/Safe.ts +++ b/packages/protocol-kit/src/Safe.ts @@ -85,7 +85,7 @@ import { asHash, asHex } from './utils/types' import { Hash, Hex, SendTransactionParameters } from 'viem' import getPasskeyOwnerAddress from './utils/passkeys/getPasskeyOwnerAddress' import createPasskeyDeploymentTransaction from './utils/passkeys/createPasskeyDeploymentTransaction' -import formatTrackId from './utils/on-chain-tracking/formatTrackId' +import generateOnChainIdentifier from './utils/on-chain-tracking/generateOnChainIdentifier' const EQ_OR_GT_1_4_1 = '>=1.4.1' const EQ_OR_GT_1_3_0 = '>=1.3.0' @@ -102,8 +102,8 @@ class Safe { #MAGIC_VALUE = '0x1626ba7e' #MAGIC_VALUE_BYTES = '0x20c13b0b' - // On-chain Analitics - #trackId: string = '' + // on-chain Analitics + #onchainIdentifier: string = '' /** * Creates an instance of the Safe Core SDK. @@ -129,10 +129,15 @@ class Safe { * @throws "MultiSendCallOnly contract is not deployed on the current network" */ async #initializeProtocolKit(config: SafeConfig) { - const { provider, signer, isL1SafeSingleton, contractNetworks, trackId } = config - - if (trackId) { - this.#trackId = formatTrackId(trackId) + const { provider, signer, isL1SafeSingleton, contractNetworks, onchainAnalitics } = config + + if (onchainAnalitics?.project) { + this.#onchainIdentifier = generateOnChainIdentifier( + onchainAnalitics.project, + onchainAnalitics.platform, + 'protocol-kit', + '5.0.4' + ) } this.#safeProvider = await SafeProvider.init({ @@ -1347,13 +1352,13 @@ class Safe { const signerAddress = await this.#safeProvider.getSignerAddress() - if (this.#trackId) { + if (this.#onchainIdentifier) { const encodedTransaction = await this.getEncodedTransaction(signedSafeTransaction) const transaction = { to: await this.getAddress(), value: 0n, - data: encodedTransaction + this.#trackId + data: encodedTransaction + this.#onchainIdentifier } const signer = await this.#safeProvider.getExternalSigner() @@ -1364,7 +1369,7 @@ class Safe { const hash = await signer.sendTransaction({ ...transaction, - account: signerAddress, + account: signer.account, ...options } as SendTransactionParameters) @@ -1499,6 +1504,14 @@ class Safe { // we create the deployment transaction const safeDeploymentTransaction = await this.createSafeDeploymentTransaction() + // remove the onchain idendifier if it is included + if (safeDeploymentTransaction.data.endsWith(this.#onchainIdentifier)) { + safeDeploymentTransaction.data = safeDeploymentTransaction.data.replace( + this.#onchainIdentifier, + '' + ) + } + // First transaction of the batch: The Safe deployment Transaction const safeDeploymentBatchTransaction = { to: safeDeploymentTransaction.to, @@ -1519,7 +1532,11 @@ class Safe { const transactions = [safeDeploymentBatchTransaction, safeBatchTransaction] // this is the transaction with the batch - const safeDeploymentBatch = await this.createTransactionBatch(transactions, transactionOptions) + const safeDeploymentBatch = await this.createTransactionBatch( + transactions, + transactionOptions, + true // include the on chain identifier + ) return safeDeploymentBatch } @@ -1587,12 +1604,15 @@ class Safe { to: safeProxyFactoryContract.getAddress(), value: '0', // we use the createProxyWithNonce method to create the Safe in a deterministic address, see: https://github.com/safe-global/safe-contracts/blob/main/contracts/proxies/SafeProxyFactory.sol#L52 - data: - safeProxyFactoryContract.encode('createProxyWithNonce', [ - asHex(safeSingletonContract.getAddress()), - asHex(initializer), // call to the setup method to set the threshold & owners of the new Safe - BigInt(saltNonce) - ]) + this.#trackId + data: safeProxyFactoryContract.encode('createProxyWithNonce', [ + asHex(safeSingletonContract.getAddress()), + asHex(initializer), // call to the setup method to set the threshold & owners of the new Safe + BigInt(saltNonce) + ]) + } + + if (this.#onchainIdentifier) { + safeDeployTransactionData.data += this.#onchainIdentifier } return safeDeployTransactionData @@ -1606,12 +1626,14 @@ class Safe { * @function createTransactionBatch * @param {MetaTransactionData[]} transactions - An array of MetaTransactionData objects to be batched together. * @param {TransactionOption} [transactionOptions] - Optional TransactionOption object to specify additional options for the transaction batch. + * @param {boolean} [includeOnchainIdentifier=false] - A flag indicating whether to append the onchain identifier to the data field of the resulting transaction. * @returns {Promise} A Promise that resolves with the created transaction batch. * */ async createTransactionBatch( transactions: MetaTransactionData[], - transactionOptions?: TransactionOptions + transactionOptions?: TransactionOptions, + includeOnchainIdentifier: boolean = false ): Promise { // we use the MultiSend contract to create the batch, see: https://github.com/safe-global/safe-contracts/blob/main/contracts/libraries/MultiSendCallOnly.sol const multiSendCallOnlyContract = this.#contractManager.multiSendCallOnlyContract @@ -1628,6 +1650,10 @@ class Safe { data: batchData } + if (includeOnchainIdentifier) { + transactionBatch.data += this.#onchainIdentifier + } + return transactionBatch } @@ -1736,7 +1762,7 @@ class Safe { } getTrackId(): string { - return this.#trackId + return this.#onchainIdentifier } } diff --git a/packages/protocol-kit/src/index.ts b/packages/protocol-kit/src/index.ts index cd12b85cf..79714063d 100644 --- a/packages/protocol-kit/src/index.ts +++ b/packages/protocol-kit/src/index.ts @@ -67,6 +67,7 @@ import { } from './utils/eip-712' import { createPasskeyClient } from './utils/passkeys/PasskeyClient' import getPasskeyOwnerAddress from './utils/passkeys/getPasskeyOwnerAddress' +import generateOnChainIdentifier from './utils/on-chain-tracking/generateOnChainIdentifier' export { estimateTxBaseGas, @@ -82,6 +83,7 @@ export { EthSafeSignature, MultiSendCallOnlyBaseContract, MultiSendBaseContract, + generateOnChainIdentifier, PREDETERMINED_SALT_NONCE, SafeBaseContract, SafeProxyFactoryBaseContract, diff --git a/packages/protocol-kit/src/types/safeConfig.ts b/packages/protocol-kit/src/types/safeConfig.ts index 9fa1b16ac..b187e36bd 100644 --- a/packages/protocol-kit/src/types/safeConfig.ts +++ b/packages/protocol-kit/src/types/safeConfig.ts @@ -41,6 +41,13 @@ type SafeConfigWithPredictedSafeProps = { predictedSafe: PredictedSafeProps } +export type OnchainAnaliticsProps = { + /** project - The project that is using the SDK */ + project?: string + /** platform - The platform that is using the SDK */ + platform?: string +} + export type SafeConfigProps = { provider: SafeProviderConfig['provider'] signer?: SafeProviderConfig['signer'] @@ -49,7 +56,7 @@ export type SafeConfigProps = { /** contractNetworks - Contract network configuration */ contractNetworks?: ContractNetworksConfig // on-chain analitics - trackId?: string + onchainAnalitics?: OnchainAnaliticsProps } export type SafeConfigWithSafeAddress = SafeConfigProps & SafeConfigWithSafeAddressProps diff --git a/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts b/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts new file mode 100644 index 000000000..6f93a9041 --- /dev/null +++ b/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts @@ -0,0 +1,41 @@ +import { keccak256, toHex } from 'viem' + +/** + * Generates a hash from the given input string and truncates it to the specified size. + * + * @param {string} input - The input string to be hashed. + * @param {number} size - The number of bytes to take from the end of the hash. + * @returns {string} A hexadecimal string representation of the truncated hash, without the `0x` prefix. + */ +export function generateHash(input: string, size: number): string { + const fullHash = keccak256(Buffer.from(input)) + return toHex(fullHash.slice(-size)).replace('0x', '') // Take the last X bytes +} + +/** + * Generates an on-chain identifier for tracking transactions on the blockchain. + * This identifier includes hased metadata such as the project name, platform, tool, and tool version. + * + * @param {string} project - The name of the project initiating the transaction. + * @param {string} [platform='Web'] - The platform from which the transaction originates (e.g., "Web", "Mobile", "Safe App", "Widget"...). + * @param {string} tool - The tool used to generate the transaction (e.g., "protocol-kit"). + * @param {string} toolVersion - The version of the tool used to generate the transaction. + * @returns {string} A string representing the on-chain identifier, composed of multiple hashed segments. + */ +function generateOnChainIdentifier( + project: string, + platform: string = 'Web', + tool: string, + toolVersion: string +): string { + const indentifierPrefix = '5afe' + const indentifierVersion = '00' // first version + const projectHash = generateHash(project, 20) // Take the last 20 bytes + const platformHash = generateHash(platform, 3) // Take the last 3 bytes + const toolHash = generateHash(tool, 3) // Take the last 3 bytes + const toolVersionHash = generateHash(toolVersion, 3) // Take the last 3 bytes + + return `${indentifierPrefix}${indentifierVersion}${projectHash}${platformHash}${toolHash}${toolVersionHash}` +} + +export default generateOnChainIdentifier diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index aa045d232..effeb4d62 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -5,7 +5,8 @@ import Safe, { encodeMultiSendData, getMultiSendContract, PasskeyClient, - SafeProvider + SafeProvider, + generateOnChainIdentifier } from '@safe-global/protocol-kit' import { RelayKitBasePack } from '@safe-global/relay-kit/RelayKitBasePack' import { @@ -87,6 +88,8 @@ export class Safe4337Pack extends RelayKitBasePack<{ #paymasterOptions?: PaymasterOptions + #onchainIdentifier: string = '' + /** * Creates an instance of the Safe4337Pack. * @@ -100,7 +103,8 @@ export class Safe4337Pack extends RelayKitBasePack<{ paymasterOptions, entryPointAddress, safe4337ModuleAddress, - safeWebAuthnSharedSignerAddress + safeWebAuthnSharedSignerAddress, + onchainAnalitics }: Safe4337Options) { super(protocolKit) @@ -111,6 +115,15 @@ export class Safe4337Pack extends RelayKitBasePack<{ this.#ENTRYPOINT_ADDRESS = entryPointAddress this.#SAFE_4337_MODULE_ADDRESS = safe4337ModuleAddress this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS = safeWebAuthnSharedSignerAddress || '0x' + + if (onchainAnalitics?.project) { + this.#onchainIdentifier = generateOnChainIdentifier( + onchainAnalitics.project, + onchainAnalitics.platform, + 'relay-kit', + '3.2.4' + ) + } } /** @@ -124,7 +137,16 @@ export class Safe4337Pack extends RelayKitBasePack<{ * @return {Promise} The Promise object that will be resolved into an instance of Safe4337Pack. */ static async init(initOptions: Safe4337InitOptions): Promise { - const { provider, signer, options, bundlerUrl, customContracts, paymasterOptions } = initOptions + const { + provider, + signer, + options, + bundlerUrl, + customContracts, + paymasterOptions, + onchainAnalitics + } = initOptions + let protocolKit: Safe const bundlerClient = getEip4337BundlerProvider(bundlerUrl) const chainId = await bundlerClient.request({ method: RPC_4337_CALLS.CHAIN_ID }) @@ -329,7 +351,8 @@ export class Safe4337Pack extends RelayKitBasePack<{ payment: 0, paymentReceiver: zeroAddress } - } + }, + onchainAnalitics }) } @@ -372,7 +395,8 @@ export class Safe4337Pack extends RelayKitBasePack<{ bundlerUrl, entryPointAddress: selectedEntryPoint!, safe4337ModuleAddress, - safeWebAuthnSharedSignerAddress + safeWebAuthnSharedSignerAddress, + onchainAnalitics }) } @@ -526,6 +550,10 @@ export class Safe4337Pack extends RelayKitBasePack<{ signature: '0x' } + if (this.#onchainIdentifier) { + userOperation.callData += this.#onchainIdentifier + } + const isSafeDeployed = await this.protocolKit.isSafeDeployed() if (!isSafeDeployed) { @@ -799,4 +827,8 @@ export class Safe4337Pack extends RelayKitBasePack<{ ] }) } + + getOnchainIdentifier(): string { + return this.#onchainIdentifier + } } diff --git a/packages/relay-kit/src/packs/safe-4337/types.ts b/packages/relay-kit/src/packs/safe-4337/types.ts index a64bda523..99a944703 100644 --- a/packages/relay-kit/src/packs/safe-4337/types.ts +++ b/packages/relay-kit/src/packs/safe-4337/types.ts @@ -1,5 +1,9 @@ import { Account, Address, Chain, Hash, Hex, PublicClient, PublicRpcSchema, Transport } from 'viem' -import Safe, { DeploymentType, SafeProviderConfig } from '@safe-global/protocol-kit' +import Safe, { + DeploymentType, + SafeProviderConfig, + OnchainAnaliticsProps +} from '@safe-global/protocol-kit' import { EstimateGasData, MetaTransactionData, @@ -50,6 +54,8 @@ export type Safe4337InitOptions = { } options: ExistingSafeOptions | PredictedSafeOptions paymasterOptions?: PaymasterOptions + // on-chain analitics + onchainAnalitics?: OnchainAnaliticsProps } export type Safe4337Options = { @@ -61,6 +67,8 @@ export type Safe4337Options = { entryPointAddress: string safe4337ModuleAddress: string safeWebAuthnSharedSignerAddress?: string + // on-chain analitics + onchainAnalitics?: OnchainAnaliticsProps } export type Safe4337CreateTransactionProps = { From d96eca4ffa33756e8557a216f2bce75f006d23fe Mon Sep 17 00:00:00 2001 From: Daniel Somoza Date: Thu, 28 Nov 2024 14:28:12 +0100 Subject: [PATCH 03/12] fix On-chain analytics e2e tests --- packages/protocol-kit/src/Safe.ts | 2 +- packages/protocol-kit/src/types/safeConfig.ts | 2 +- .../utils/on-chain-tracking/formatTrackId.ts | 15 -- .../generateOnChainIdentifier.ts | 6 +- .../tests/e2e/onChainIdentifier.test.ts | 156 ++++++++++++++++++ .../tests/e2e/onChainTrackId.test.ts | 118 ------------- .../create-execute-transaction.ts | 15 +- playground/protocol-kit/deploy-safe.ts | 18 +- ...-transfer-4337-sponsored-counterfactual.ts | 39 +++-- 9 files changed, 209 insertions(+), 162 deletions(-) delete mode 100644 packages/protocol-kit/src/utils/on-chain-tracking/formatTrackId.ts create mode 100644 packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts delete mode 100644 packages/protocol-kit/tests/e2e/onChainTrackId.test.ts diff --git a/packages/protocol-kit/src/Safe.ts b/packages/protocol-kit/src/Safe.ts index 636daa84b..47702a4c5 100644 --- a/packages/protocol-kit/src/Safe.ts +++ b/packages/protocol-kit/src/Safe.ts @@ -1761,7 +1761,7 @@ class Safe { return getContractInfo(contractAddress) } - getTrackId(): string { + getOnchainIdentifier(): string { return this.#onchainIdentifier } } diff --git a/packages/protocol-kit/src/types/safeConfig.ts b/packages/protocol-kit/src/types/safeConfig.ts index b187e36bd..7b178b7dd 100644 --- a/packages/protocol-kit/src/types/safeConfig.ts +++ b/packages/protocol-kit/src/types/safeConfig.ts @@ -85,7 +85,7 @@ type ConnectSafeConfigProps = { /** contractNetworks - Contract network configuration */ contractNetworks?: ContractNetworksConfig // on-chain analitics - trackId?: string + onchainAnalitics?: OnchainAnaliticsProps } export type ConnectSafeConfigWithSafeAddress = ConnectSafeConfigProps & diff --git a/packages/protocol-kit/src/utils/on-chain-tracking/formatTrackId.ts b/packages/protocol-kit/src/utils/on-chain-tracking/formatTrackId.ts deleted file mode 100644 index 7d6ee67ca..000000000 --- a/packages/protocol-kit/src/utils/on-chain-tracking/formatTrackId.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { keccak256, toHex } from 'viem' - -/** - * Converts a trackId into a hash for tracking Safe transactions and deployments on a blockchain. - * This function converts the provided trackId to hexadecimal format, hashes it using keccak256, - * and returns the first 40 characters of the hash (excluding the '0x' prefix). - * - * @param {string} trackId - The identifier provided by the user to be formatted into a hash. - * @returns {string} - A 40-character hexadecimal string representing the hash of the trackId. - */ -function formatTrackId(trackId: string): string { - return keccak256(toHex(trackId)).substring(2, 42) // only first 40 chars from the hash (excluding the '0x' prefix) -} - -export default formatTrackId diff --git a/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts b/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts index 6f93a9041..1a159367e 100644 --- a/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts +++ b/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts @@ -28,14 +28,14 @@ function generateOnChainIdentifier( tool: string, toolVersion: string ): string { - const indentifierPrefix = '5afe' - const indentifierVersion = '00' // first version + const identifierPrefix = '5afe' + const identifierVersion = '00' // first version const projectHash = generateHash(project, 20) // Take the last 20 bytes const platformHash = generateHash(platform, 3) // Take the last 3 bytes const toolHash = generateHash(tool, 3) // Take the last 3 bytes const toolVersionHash = generateHash(toolVersion, 3) // Take the last 3 bytes - return `${indentifierPrefix}${indentifierVersion}${projectHash}${platformHash}${toolHash}${toolVersionHash}` + return `${identifierPrefix}${identifierVersion}${projectHash}${platformHash}${toolHash}${toolVersionHash}` } export default generateOnChainIdentifier diff --git a/packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts b/packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts new file mode 100644 index 000000000..cf7e8784d --- /dev/null +++ b/packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts @@ -0,0 +1,156 @@ +import { setupTests, safeVersionDeployed } from '@safe-global/testing-kit' +import Safe, { + generateOnChainIdentifier, + OnchainAnaliticsProps, + PredictedSafeProps, + SafeAccountConfig +} from '@safe-global/protocol-kit/index' +import chai from 'chai' +import chaiAsPromised from 'chai-as-promised' + +import { getEip1193Provider } from './utils/setupProvider' +import { waitSafeTxReceipt } from './utils/transactions' +import { generateHash } from '@safe-global/protocol-kit/utils/on-chain-tracking/generateOnChainIdentifier' + +chai.use(chaiAsPromised) + +describe('On-chain analytics', () => { + const provider = getEip1193Provider() + + describe('generateOnChainIdentifier fn', () => { + it('should return the correct on-chain identifier format', async () => { + const project = 'Test e2e Project' + const platform = 'Web' + const tool = 'protocol-kit' + const toolVersion = '1.0.0' + + const onChainIdentifier = generateOnChainIdentifier(project, platform, tool, toolVersion) + + const identifierPrefix = '5afe' + const identifierVersion = '00' + + chai.expect(onChainIdentifier.startsWith(identifierPrefix)).to.be.true + chai.expect(onChainIdentifier.substring(4, 6)).to.equals(identifierVersion) + chai.expect(onChainIdentifier.substring(6, 46)).to.equals(generateHash(project, 20)) + chai.expect(onChainIdentifier.substring(46, 52)).to.equals(generateHash(platform, 3)) + chai.expect(onChainIdentifier.substring(52, 58)).to.equals(generateHash(tool, 3)) + chai.expect(onChainIdentifier.substring(58, 64)).to.equals(generateHash(toolVersion, 3)) + }) + }) + + describe('getOnchainIdentifier method', () => { + it('should return the on-chain identifier when provided', async () => { + const onchainAnalitics: OnchainAnaliticsProps = { + project: 'Test e2e Project', + platform: 'Web' + } + + const { safe, contractNetworks } = await setupTests() + const safeAddress = safe.address + + const protocolKit = await Safe.init({ + provider, + safeAddress, + contractNetworks, + onchainAnalitics + }) + + const onChainIdentifier = '5afe003861653435366632366138366164643038373864646561393238653366' + + chai.expect(onChainIdentifier).to.equals(protocolKit.getOnchainIdentifier()) + }) + + it('should return an empty string when no onchain Analiticts is provided', async () => { + const { safe, contractNetworks } = await setupTests() + const safeAddress = safe.address + + const protocolKit = await Safe.init({ + provider, + safeAddress, + contractNetworks + }) + + chai.expect(protocolKit.getOnchainIdentifier()).to.empty + }) + }) + + describe('Tracking Safe Deployment on-chain via the transaction data field', () => { + it('should append the on-chain identifier to the deployment transaction data field', async () => { + const onchainAnalitics: OnchainAnaliticsProps = { + project: 'Test e2e Project', + platform: 'Web' + } + + const { accounts, contractNetworks } = await setupTests() + const [account1, account2] = accounts + const owners = [account1.address, account2.address] + const threshold = 1 + const safeAccountConfig: SafeAccountConfig = { owners, threshold } + const predictedSafe: PredictedSafeProps = { + safeAccountConfig, + safeDeploymentConfig: { + safeVersion: safeVersionDeployed + } + } + + const protocolKit = await Safe.init({ + provider, + predictedSafe, + contractNetworks, + onchainAnalitics + }) + + const deploymentTransaction = await protocolKit.createSafeDeploymentTransaction() + + const onChainIdentifier = '5afe003861653435366632366138366164643038373864646561393238653366' + + chai.expect(onChainIdentifier).to.equals(protocolKit.getOnchainIdentifier()) + chai.expect(deploymentTransaction.data.endsWith(onChainIdentifier)).to.be.true + }) + }) + + describe('Tracking Safe transactions on-chain via the transaction data field', () => { + it('should append the on-chain identifier to the execTransaction data field', async () => { + const onchainAnalitics: OnchainAnaliticsProps = { + project: 'Test e2e Project', + platform: 'Web' + } + + const { safe, contractNetworks } = await setupTests() + const safeAddress = safe.address + + const protocolKit = await Safe.init({ + provider, + safeAddress, + contractNetworks, + onchainAnalitics + }) + + const testTransaction = { + to: safeAddress, + value: '0', + data: '0x' + } + + const safeTransaction = await protocolKit.createTransaction({ + transactions: [testTransaction] + }) + + const isValidTransaction = await protocolKit.isValidTransaction(safeTransaction) + chai.expect(isValidTransaction).to.be.eq(true) + + const transactionResponse = await protocolKit.executeTransaction(safeTransaction) + + await waitSafeTxReceipt(transactionResponse) + + const transaction = await protocolKit + .getSafeProvider() + .getTransaction(transactionResponse.hash) + + const onChainIdentifier = '5afe003861653435366632366138366164643038373864646561393238653366' + + chai.expect(onChainIdentifier).to.equals(protocolKit.getOnchainIdentifier()) + chai.expect(transaction.input.endsWith(onChainIdentifier)).to.be.true + }) + }) +}) diff --git a/packages/protocol-kit/tests/e2e/onChainTrackId.test.ts b/packages/protocol-kit/tests/e2e/onChainTrackId.test.ts deleted file mode 100644 index 73f416bf3..000000000 --- a/packages/protocol-kit/tests/e2e/onChainTrackId.test.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { setupTests, safeVersionDeployed } from '@safe-global/testing-kit' -import Safe, { PredictedSafeProps, SafeAccountConfig } from '@safe-global/protocol-kit/index' -import chai from 'chai' -import chaiAsPromised from 'chai-as-promised' - -import { getEip1193Provider } from './utils/setupProvider' -import { waitSafeTxReceipt } from './utils/transactions' - -chai.use(chaiAsPromised) - -describe('On-chain analytics', () => { - const provider = getEip1193Provider() - - describe('getTrackId method', () => { - it('should return the correctly formatted track id when provided', async () => { - const trackId = 'test-track-id' - const { safe, contractNetworks } = await setupTests() - const safeAddress = safe.address - - const protocolKit = await Safe.init({ - provider, - safeAddress, - contractNetworks, - trackId - }) - - const formattedTrackId = '7ba67a7e86c9fad9f51790e7e60307f882b9c492' - - chai.expect(formattedTrackId).to.equals(protocolKit.getTrackId()) - }) - - it('should return an empty string when no track id is provided', async () => { - const { safe, contractNetworks } = await setupTests() - const safeAddress = safe.address - - const protocolKit = await Safe.init({ - provider, - safeAddress, - contractNetworks - }) - - chai.expect(protocolKit.getTrackId()).to.empty - }) - }) - - describe('Tracking Safe Deployment on Chain via the transaction data field', () => { - it('should append the formatted trackId to the deployment transaction data field', async () => { - const trackId = 'test-track-id' - - const { accounts, contractNetworks } = await setupTests() - const [account1, account2] = accounts - const owners = [account1.address, account2.address] - const threshold = 1 - const safeAccountConfig: SafeAccountConfig = { owners, threshold } - const predictedSafe: PredictedSafeProps = { - safeAccountConfig, - safeDeploymentConfig: { - safeVersion: safeVersionDeployed - } - } - - const protocolKit = await Safe.init({ - provider, - predictedSafe, - contractNetworks, - trackId - }) - - const deploymentTransaction = await protocolKit.createSafeDeploymentTransaction() - - const formattedTrackId = '7ba67a7e86c9fad9f51790e7e60307f882b9c492' - - chai.expect(formattedTrackId).to.equals(protocolKit.getTrackId()) - chai.expect(deploymentTransaction.data.endsWith(formattedTrackId)).to.be.true - }) - }) - - describe('Tracking Safe transactions on Chain via the transaction data field', () => { - it('should append the formatted trackId to the exec transaction data field', async () => { - const trackId = 'test-track-id' - const { safe, contractNetworks } = await setupTests() - const safeAddress = safe.address - - const protocolKit = await Safe.init({ - provider, - safeAddress, - contractNetworks, - trackId - }) - - const testTransaction = { - to: safeAddress, - value: '0', - data: '0x' - } - - const safeTransaction = await protocolKit.createTransaction({ - transactions: [testTransaction] - }) - - const isValidTransaction = await protocolKit.isValidTransaction(safeTransaction) - chai.expect(isValidTransaction).to.be.eq(true) - - const transactionResponse = await protocolKit.executeTransaction(safeTransaction) - - await waitSafeTxReceipt(transactionResponse) - - const transaction = await protocolKit - .getSafeProvider() - .getTransaction(transactionResponse.hash) - - const formattedTrackId = '7ba67a7e86c9fad9f51790e7e60307f882b9c492' - - chai.expect(formattedTrackId).to.equals(protocolKit.getTrackId()) - chai.expect(transaction.input.endsWith(formattedTrackId)).to.be.true - }) - }) -}) diff --git a/playground/protocol-kit/create-execute-transaction.ts b/playground/protocol-kit/create-execute-transaction.ts index 52e00230b..c0c58c7fb 100644 --- a/playground/protocol-kit/create-execute-transaction.ts +++ b/playground/protocol-kit/create-execute-transaction.ts @@ -18,9 +18,11 @@ interface Config { } const config: Config = { - RPC_URL: 'https://sepolia.gateway.tenderly.co', + // RPC_URL: 'https://sepolia.gateway.tenderly.co', + RPC_URL: 'https://sepolia.infura.io/v3/ab83b04f41414799a57129cb165be0dd', + // RPC_URL: 'https://eth-sepolia.public.blastapi.io', SIGNER_ADDRESS_PRIVATE_KEY: SIGNER_ADDRESS_PRIVATE_KEY!, - SAFE_ADDRESS: '' + SAFE_ADDRESS: '0x096B2222AE1600D0074d35a8A6BC58FF04AB2384' } async function main() { @@ -28,7 +30,8 @@ async function main() { const safe = await Safe.init({ provider: config.RPC_URL, signer: config.SIGNER_ADDRESS_PRIVATE_KEY, - safeAddress: config.SAFE_ADDRESS + safeAddress: config.SAFE_ADDRESS, + onchainAnalitics: { project: 'Test Dapp SDK', platform: 'Web' } }) console.log('Creating transaction with Safe:') @@ -37,10 +40,12 @@ async function main() { console.log(' - Version: ', await safe.getContractVersion()) console.log(' - Threshold: ', await safe.getThreshold(), '\n') - // Create transaction + console.log('On Chain identifier: ', safe.getTrackId()) + + // Create rejection transaction const safeTransactionData: SafeTransactionDataPartial = { to: config.SAFE_ADDRESS, - value: '1000000000000000', // 0.001 ether + value: '0', // 0.001 ether data: '0x', operation: OperationType.Call } diff --git a/playground/protocol-kit/deploy-safe.ts b/playground/protocol-kit/deploy-safe.ts index eadbf568b..48fb9dae0 100644 --- a/playground/protocol-kit/deploy-safe.ts +++ b/playground/protocol-kit/deploy-safe.ts @@ -1,3 +1,4 @@ +import * as dotenv from 'dotenv' import Safe, { SafeAccountConfig, getSafeAddressFromDeploymentTx } from '@safe-global/protocol-kit' import { SafeVersion } from '@safe-global/types-kit' @@ -7,6 +8,10 @@ import { sepolia } from 'viem/chains' import { waitForTransactionReceipt } from 'viem/actions' import semverSatisfies from 'semver/functions/satisfies' +dotenv.config() + +const { SIGNER_ADDRESS_PRIVATE_KEY } = process.env + // This file can be used to play around with the Safe Core SDK interface Config { @@ -21,12 +26,12 @@ interface Config { } const config: Config = { - RPC_URL: sepolia.rpcUrls.default.http[0], - DEPLOYER_ADDRESS_PRIVATE_KEY: '', + RPC_URL: 'https://sepolia.infura.io/v3/ab83b04f41414799a57129cb165be0dd', // SEPOLIA + DEPLOYER_ADDRESS_PRIVATE_KEY: SIGNER_ADDRESS_PRIVATE_KEY!, DEPLOY_SAFE: { - OWNERS: ['OWNER_ADDRESS'], + OWNERS: ['0x0Ee26C4481485AC64BfFf2bdCaA21EdAeCEcdCa9'], THRESHOLD: 1, // - SALT_NONCE: '150000', + SALT_NONCE: '1500002332234342345', SAFE_VERSION: '1.3.0' } } @@ -53,9 +58,12 @@ async function main() { saltNonce, safeVersion } - } + }, + onchainAnalitics: { project: 'Test Dapp SDK', platform: 'Web' } }) + console.log('On Chain identifier: ', protocolKit.getTrackId()) + // The Account Abstraction feature is only available for Safes version 1.3.0 and above. if (semverSatisfies(safeVersion, '>=1.3.0')) { // check if its deployed diff --git a/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts b/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts index abf437791..3e006ccf4 100644 --- a/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts +++ b/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts @@ -3,12 +3,12 @@ import { Safe4337Pack } from '@safe-global/relay-kit' import { waitForOperationToFinish, transfer, generateTransferCallData } from '../utils' // Safe owner PK -const PRIVATE_KEY = '' +const PRIVATE_KEY = '77ad1d85be4115ab08f3f31ce08fd9473896e9ba008115f5f49c6c7e9183a501' -const PIMLICO_API_KEY = '' +const PIMLICO_API_KEY = 'f8492c09-7cad-43ed-83d3-548a3692583d' // Safe owner address -const OWNER_ADDRESS = '' +const OWNER_ADDRESS = '0x0Ee26C4481485AC64BfFf2bdCaA21EdAeCEcdCa9' // PolicyId is an optional parameter, you can create one here: https://dashboard.pimlico.io/sponsorship-policies const POLICY_ID = '' @@ -18,7 +18,8 @@ const CHAIN_NAME = 'sepolia' // const CHAIN_NAME = 'gnosis' // RPC URL -const RPC_URL = 'https://rpc.sepolia.org' // SEPOLIA +const RPC_URL = 'https://sepolia.infura.io/v3/ab83b04f41414799a57129cb165be0dd' // SEPOLIA +// const RPC_URL = 'https://rpc.sepolia.org' // SEPOLIA // const RPC_URL = 'https://rpc.gnosischain.com/' // GNOSIS // Bundler URL @@ -46,14 +47,17 @@ async function main() { options: { owners: [OWNER_ADDRESS], threshold: 1, - saltNonce: '4337' + '1' // to update the address - } + saltNonce: '4337' + '222334434432432' // to update the address + }, + onchainAnalitics: { project: 'Test Dapp SDK', platform: 'Web' } }) // Log supported entry points and chain id console.log('Supported Entry Points', await safe4337Pack.getSupportedEntryPoints()) console.log('Chain Id', await safe4337Pack.getChainId()) + console.log('onchain Identifier: ', safe4337Pack.getOnchainIdentifier()) + // Create transaction batch with two 0.1 USDC transfers const senderAddress = await safe4337Pack.protocolKit.getAddress() @@ -61,12 +65,12 @@ async function main() { console.log('is Safe Account deployed: ', await safe4337Pack.protocolKit.isSafeDeployed()) - const usdcAmount = 100_000n // 0.1 USDC + const usdcAmount = 10_000n // 0.01 USDC console.log(`sending USDC...`) const externalSigner = await safe4337Pack.protocolKit.getSafeProvider().getExternalSigner() - const externalProvider = safe4337Pack.protocolKit.getSafeProvider().getExternalProvider() + // const externalProvider = safe4337Pack.protocolKit.getSafeProvider().getExternalProvider() if (!externalSigner) { throw new Error('No signer found!') @@ -83,15 +87,22 @@ async function main() { value: '0' } const transactions = [transferUSDC, transferUSDC] - const timestamp = (await getBlock(externalProvider))?.timestamp || 0n + + // const rejectTransaction = { + // to: senderAddress, + // data: '0x', + // value: '0' + // } + // const transactions = [rejectTransaction, rejectTransaction] + // const timestamp = (await getBlock(externalProvider))?.timestamp || 0n // 2) Create transaction batch const safeOperation = await safe4337Pack.createTransaction({ - transactions, - options: { - validAfter: Number(timestamp - 60_000n), - validUntil: Number(timestamp + 60_000n) - } + transactions + // options: { + // validAfter: Number(timestamp - 60_000n), + // validUntil: Number(timestamp + 60_000n) + // } }) // 3) Sign SafeOperation From f0057b8ef4b4b22b3a040c04fa185d688ba6f471 Mon Sep 17 00:00:00 2001 From: Daniel Somoza Date: Thu, 28 Nov 2024 14:37:52 +0100 Subject: [PATCH 04/12] restored playground file --- ...-transfer-4337-sponsored-counterfactual.ts | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts b/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts index 3e006ccf4..bf8e41dd8 100644 --- a/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts +++ b/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts @@ -3,12 +3,12 @@ import { Safe4337Pack } from '@safe-global/relay-kit' import { waitForOperationToFinish, transfer, generateTransferCallData } from '../utils' // Safe owner PK -const PRIVATE_KEY = '77ad1d85be4115ab08f3f31ce08fd9473896e9ba008115f5f49c6c7e9183a501' +const PRIVATE_KEY = '' const PIMLICO_API_KEY = 'f8492c09-7cad-43ed-83d3-548a3692583d' // Safe owner address -const OWNER_ADDRESS = '0x0Ee26C4481485AC64BfFf2bdCaA21EdAeCEcdCa9' +const OWNER_ADDRESS = '' // PolicyId is an optional parameter, you can create one here: https://dashboard.pimlico.io/sponsorship-policies const POLICY_ID = '' @@ -18,8 +18,7 @@ const CHAIN_NAME = 'sepolia' // const CHAIN_NAME = 'gnosis' // RPC URL -const RPC_URL = 'https://sepolia.infura.io/v3/ab83b04f41414799a57129cb165be0dd' // SEPOLIA -// const RPC_URL = 'https://rpc.sepolia.org' // SEPOLIA +const RPC_URL = 'https://rpc.sepolia.org' // SEPOLIA // const RPC_URL = 'https://rpc.gnosischain.com/' // GNOSIS // Bundler URL @@ -48,16 +47,13 @@ async function main() { owners: [OWNER_ADDRESS], threshold: 1, saltNonce: '4337' + '222334434432432' // to update the address - }, - onchainAnalitics: { project: 'Test Dapp SDK', platform: 'Web' } + } }) // Log supported entry points and chain id console.log('Supported Entry Points', await safe4337Pack.getSupportedEntryPoints()) console.log('Chain Id', await safe4337Pack.getChainId()) - console.log('onchain Identifier: ', safe4337Pack.getOnchainIdentifier()) - // Create transaction batch with two 0.1 USDC transfers const senderAddress = await safe4337Pack.protocolKit.getAddress() @@ -70,7 +66,7 @@ async function main() { console.log(`sending USDC...`) const externalSigner = await safe4337Pack.protocolKit.getSafeProvider().getExternalSigner() - // const externalProvider = safe4337Pack.protocolKit.getSafeProvider().getExternalProvider() + const externalProvider = safe4337Pack.protocolKit.getSafeProvider().getExternalProvider() if (!externalSigner) { throw new Error('No signer found!') @@ -88,21 +84,15 @@ async function main() { } const transactions = [transferUSDC, transferUSDC] - // const rejectTransaction = { - // to: senderAddress, - // data: '0x', - // value: '0' - // } - // const transactions = [rejectTransaction, rejectTransaction] - // const timestamp = (await getBlock(externalProvider))?.timestamp || 0n + const timestamp = (await getBlock(externalProvider))?.timestamp || 0n // 2) Create transaction batch const safeOperation = await safe4337Pack.createTransaction({ - transactions - // options: { - // validAfter: Number(timestamp - 60_000n), - // validUntil: Number(timestamp + 60_000n) - // } + transactions, + options: { + validAfter: Number(timestamp - 60_000n), + validUntil: Number(timestamp + 60_000n) + } }) // 3) Sign SafeOperation From 96704bbfb2b79f02d66f34110805824a834c9865 Mon Sep 17 00:00:00 2001 From: Daniel Somoza Date: Thu, 28 Nov 2024 14:38:30 +0100 Subject: [PATCH 05/12] remove PIMLICO_API_KEY from playground --- .../relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts b/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts index bf8e41dd8..e5e2b76a7 100644 --- a/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts +++ b/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts @@ -5,7 +5,7 @@ import { waitForOperationToFinish, transfer, generateTransferCallData } from '.. // Safe owner PK const PRIVATE_KEY = '' -const PIMLICO_API_KEY = 'f8492c09-7cad-43ed-83d3-548a3692583d' +const PIMLICO_API_KEY = '' // Safe owner address const OWNER_ADDRESS = '' From 7de2f15051cf2d2c407025b3f228d5aeb6b8352d Mon Sep 17 00:00:00 2001 From: Daniel Somoza Date: Thu, 28 Nov 2024 14:42:18 +0100 Subject: [PATCH 06/12] restored playground files --- .../protocol-kit/create-execute-transaction.ts | 13 ++++--------- playground/protocol-kit/deploy-safe.ts | 11 ++++------- .../usdc-transfer-4337-sponsored-counterfactual.ts | 5 ++--- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/playground/protocol-kit/create-execute-transaction.ts b/playground/protocol-kit/create-execute-transaction.ts index c0c58c7fb..e4a9a3b9a 100644 --- a/playground/protocol-kit/create-execute-transaction.ts +++ b/playground/protocol-kit/create-execute-transaction.ts @@ -18,11 +18,9 @@ interface Config { } const config: Config = { - // RPC_URL: 'https://sepolia.gateway.tenderly.co', - RPC_URL: 'https://sepolia.infura.io/v3/ab83b04f41414799a57129cb165be0dd', - // RPC_URL: 'https://eth-sepolia.public.blastapi.io', + RPC_URL: 'https://sepolia.gateway.tenderly.co', SIGNER_ADDRESS_PRIVATE_KEY: SIGNER_ADDRESS_PRIVATE_KEY!, - SAFE_ADDRESS: '0x096B2222AE1600D0074d35a8A6BC58FF04AB2384' + SAFE_ADDRESS: '' } async function main() { @@ -30,8 +28,7 @@ async function main() { const safe = await Safe.init({ provider: config.RPC_URL, signer: config.SIGNER_ADDRESS_PRIVATE_KEY, - safeAddress: config.SAFE_ADDRESS, - onchainAnalitics: { project: 'Test Dapp SDK', platform: 'Web' } + safeAddress: config.SAFE_ADDRESS }) console.log('Creating transaction with Safe:') @@ -40,12 +37,10 @@ async function main() { console.log(' - Version: ', await safe.getContractVersion()) console.log(' - Threshold: ', await safe.getThreshold(), '\n') - console.log('On Chain identifier: ', safe.getTrackId()) - // Create rejection transaction const safeTransactionData: SafeTransactionDataPartial = { to: config.SAFE_ADDRESS, - value: '0', // 0.001 ether + value: '1000000000000000', // 0.001 ether data: '0x', operation: OperationType.Call } diff --git a/playground/protocol-kit/deploy-safe.ts b/playground/protocol-kit/deploy-safe.ts index 48fb9dae0..4db8f9427 100644 --- a/playground/protocol-kit/deploy-safe.ts +++ b/playground/protocol-kit/deploy-safe.ts @@ -26,12 +26,12 @@ interface Config { } const config: Config = { - RPC_URL: 'https://sepolia.infura.io/v3/ab83b04f41414799a57129cb165be0dd', // SEPOLIA + RPC_URL: sepolia.rpcUrls.default.http[0], DEPLOYER_ADDRESS_PRIVATE_KEY: SIGNER_ADDRESS_PRIVATE_KEY!, DEPLOY_SAFE: { - OWNERS: ['0x0Ee26C4481485AC64BfFf2bdCaA21EdAeCEcdCa9'], + OWNERS: ['OWNER_ADDRESS'], THRESHOLD: 1, // - SALT_NONCE: '1500002332234342345', + SALT_NONCE: '150000', SAFE_VERSION: '1.3.0' } } @@ -58,12 +58,9 @@ async function main() { saltNonce, safeVersion } - }, - onchainAnalitics: { project: 'Test Dapp SDK', platform: 'Web' } + } }) - console.log('On Chain identifier: ', protocolKit.getTrackId()) - // The Account Abstraction feature is only available for Safes version 1.3.0 and above. if (semverSatisfies(safeVersion, '>=1.3.0')) { // check if its deployed diff --git a/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts b/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts index e5e2b76a7..abf437791 100644 --- a/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts +++ b/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts @@ -46,7 +46,7 @@ async function main() { options: { owners: [OWNER_ADDRESS], threshold: 1, - saltNonce: '4337' + '222334434432432' // to update the address + saltNonce: '4337' + '1' // to update the address } }) @@ -61,7 +61,7 @@ async function main() { console.log('is Safe Account deployed: ', await safe4337Pack.protocolKit.isSafeDeployed()) - const usdcAmount = 10_000n // 0.01 USDC + const usdcAmount = 100_000n // 0.1 USDC console.log(`sending USDC...`) @@ -83,7 +83,6 @@ async function main() { value: '0' } const transactions = [transferUSDC, transferUSDC] - const timestamp = (await getBlock(externalProvider))?.timestamp || 0n // 2) Create transaction batch From 851e75eb28948c53a3bb1f80d88dbd67b4fa1cc6 Mon Sep 17 00:00:00 2001 From: Daniel Somoza Date: Thu, 28 Nov 2024 16:57:48 +0100 Subject: [PATCH 07/12] use toHex instead of Buffer.from in generateHash --- .../src/utils/on-chain-tracking/generateOnChainIdentifier.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts b/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts index 1a159367e..b5f87cfbe 100644 --- a/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts +++ b/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts @@ -8,7 +8,7 @@ import { keccak256, toHex } from 'viem' * @returns {string} A hexadecimal string representation of the truncated hash, without the `0x` prefix. */ export function generateHash(input: string, size: number): string { - const fullHash = keccak256(Buffer.from(input)) + const fullHash = keccak256(toHex(input)) return toHex(fullHash.slice(-size)).replace('0x', '') // Take the last X bytes } From a80d468cfadc5a4a048d7ad1bcca202cb74b202f Mon Sep 17 00:00:00 2001 From: Daniel Somoza Date: Tue, 10 Dec 2024 12:16:19 +0100 Subject: [PATCH 08/12] Updated generateOnChainIdentifier to accept an object and addressed PR feedback. --- packages/protocol-kit/src/Safe.ts | 14 ++++--- .../src/utils/getProtocolKitVersion.ts | 7 ++++ .../generateOnChainIdentifier.ts | 38 +++++++++++++------ .../tests/e2e/onChainIdentifier.test.ts | 2 +- packages/protocol-kit/tsconfig.build.json | 2 +- packages/protocol-kit/tsconfig.json | 2 +- .../src/packs/safe-4337/Safe4337Pack.ts | 14 ++++--- .../relay-kit/src/packs/safe-4337/types.ts | 2 - .../safe-4337/utils/getRelayKitVersion.ts | 7 ++++ packages/relay-kit/tsconfig.build.json | 2 +- packages/relay-kit/tsconfig.json | 2 +- playground/protocol-kit/deploy-safe.ts | 7 +--- 12 files changed, 63 insertions(+), 36 deletions(-) create mode 100644 packages/protocol-kit/src/utils/getProtocolKitVersion.ts create mode 100644 packages/relay-kit/src/packs/safe-4337/utils/getRelayKitVersion.ts diff --git a/packages/protocol-kit/src/Safe.ts b/packages/protocol-kit/src/Safe.ts index 20a34a9bd..2a1c78d38 100644 --- a/packages/protocol-kit/src/Safe.ts +++ b/packages/protocol-kit/src/Safe.ts @@ -88,6 +88,7 @@ import { Hash, Hex, SendTransactionParameters } from 'viem' import getPasskeyOwnerAddress from './utils/passkeys/getPasskeyOwnerAddress' import createPasskeyDeploymentTransaction from './utils/passkeys/createPasskeyDeploymentTransaction' import generateOnChainIdentifier from './utils/on-chain-tracking/generateOnChainIdentifier' +import getProtocolKitVersion from './utils/getProtocolKitVersion' const EQ_OR_GT_1_4_1 = '>=1.4.1' const EQ_OR_GT_1_3_0 = '>=1.3.0' @@ -134,12 +135,13 @@ class Safe { const { provider, signer, isL1SafeSingleton, contractNetworks, onchainAnalitics } = config if (onchainAnalitics?.project) { - this.#onchainIdentifier = generateOnChainIdentifier( - onchainAnalitics.project, - onchainAnalitics.platform, - 'protocol-kit', - '5.0.4' - ) + const { project, platform } = onchainAnalitics + this.#onchainIdentifier = generateOnChainIdentifier({ + project, + platform, + tool: 'protocol-kit', + toolVersion: getProtocolKitVersion() + }) } this.#safeProvider = await SafeProvider.init({ diff --git a/packages/protocol-kit/src/utils/getProtocolKitVersion.ts b/packages/protocol-kit/src/utils/getProtocolKitVersion.ts new file mode 100644 index 000000000..3abd3bdb4 --- /dev/null +++ b/packages/protocol-kit/src/utils/getProtocolKitVersion.ts @@ -0,0 +1,7 @@ +import packageJson from '../../package.json' + +function getProtocolKitVersion(): string { + return packageJson.version +} + +export default getProtocolKitVersion diff --git a/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts b/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts index b5f87cfbe..aee82d29f 100644 --- a/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts +++ b/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts @@ -12,22 +12,38 @@ export function generateHash(input: string, size: number): string { return toHex(fullHash.slice(-size)).replace('0x', '') // Take the last X bytes } +export type OnChainIdentifierParamsType = { + project: string + platform?: string + tool: string + toolVersion: string +} + /** * Generates an on-chain identifier for tracking transactions on the blockchain. - * This identifier includes hased metadata such as the project name, platform, tool, and tool version. + * This identifier includes hashed metadata such as the project name, platform, tool, and tool version. * - * @param {string} project - The name of the project initiating the transaction. - * @param {string} [platform='Web'] - The platform from which the transaction originates (e.g., "Web", "Mobile", "Safe App", "Widget"...). - * @param {string} tool - The tool used to generate the transaction (e.g., "protocol-kit"). - * @param {string} toolVersion - The version of the tool used to generate the transaction. + * @param {Object} params - An object containing the metadata for generating the on-chain identifier. + * @param {string} params.project - The name of the project initiating the transaction. + * @param {string} [params.platform='Web'] - The platform from which the transaction originates (e.g., "Web", "Mobile", "Safe App", "Widget"...). + * @param {string} params.tool - The tool used to generate the transaction (e.g., "protocol-kit"). + * @param {string} params.toolVersion - The version of the tool used to generate the transaction. * @returns {string} A string representing the on-chain identifier, composed of multiple hashed segments. + * + * @example + * const identifier = generateOnChainIdentifier({ + * project: 'MyProject', + * platform: 'Mobile', + * tool: 'protocol-kit', + * toolVersion: '4.0.0' + * }) */ -function generateOnChainIdentifier( - project: string, - platform: string = 'Web', - tool: string, - toolVersion: string -): string { +function generateOnChainIdentifier({ + project, + platform = 'Web', + tool, + toolVersion +}: OnChainIdentifierParamsType): string { const identifierPrefix = '5afe' const identifierVersion = '00' // first version const projectHash = generateHash(project, 20) // Take the last 20 bytes diff --git a/packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts b/packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts index cf7e8784d..a4d0eadda 100644 --- a/packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts +++ b/packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts @@ -24,7 +24,7 @@ describe('On-chain analytics', () => { const tool = 'protocol-kit' const toolVersion = '1.0.0' - const onChainIdentifier = generateOnChainIdentifier(project, platform, tool, toolVersion) + const onChainIdentifier = generateOnChainIdentifier({ project, platform, tool, toolVersion }) const identifierPrefix = '5afe' const identifierVersion = '00' diff --git a/packages/protocol-kit/tsconfig.build.json b/packages/protocol-kit/tsconfig.build.json index c05e497c4..51551a680 100644 --- a/packages/protocol-kit/tsconfig.build.json +++ b/packages/protocol-kit/tsconfig.build.json @@ -4,5 +4,5 @@ "composite": true, "outDir": "dist" }, - "include": ["src/**/*"] + "include": ["src/**/*", "package.json"] } diff --git a/packages/protocol-kit/tsconfig.json b/packages/protocol-kit/tsconfig.json index 5f6bc90a7..fc2fc6868 100644 --- a/packages/protocol-kit/tsconfig.json +++ b/packages/protocol-kit/tsconfig.json @@ -4,5 +4,5 @@ "composite": true, "outDir": "dist" }, - "include": ["src/**/*", "tests/**/*", "hardhat/**/*", "hardhat.config.ts"] + "include": ["package.json", "src/**/*", "tests/**/*", "hardhat/**/*", "hardhat.config.ts"] } diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index effeb4d62..df55fd2ac 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -53,6 +53,7 @@ import { } from './utils' import { entryPointToSafeModules, EQ_OR_GT_0_3_0 } from './utils/entrypoint' import { PimlicoFeeEstimator } from './estimators/PimlicoFeeEstimator' +import getRelayKitVersion from './utils/getRelayKitVersion' const MAX_ERC20_AMOUNT_TO_APPROVE = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn @@ -117,12 +118,13 @@ export class Safe4337Pack extends RelayKitBasePack<{ this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS = safeWebAuthnSharedSignerAddress || '0x' if (onchainAnalitics?.project) { - this.#onchainIdentifier = generateOnChainIdentifier( - onchainAnalitics.project, - onchainAnalitics.platform, - 'relay-kit', - '3.2.4' - ) + const { project, platform } = onchainAnalitics + this.#onchainIdentifier = generateOnChainIdentifier({ + project, + platform, + tool: 'relay-kit', + toolVersion: getRelayKitVersion() + }) } } diff --git a/packages/relay-kit/src/packs/safe-4337/types.ts b/packages/relay-kit/src/packs/safe-4337/types.ts index 99a944703..ea555e17f 100644 --- a/packages/relay-kit/src/packs/safe-4337/types.ts +++ b/packages/relay-kit/src/packs/safe-4337/types.ts @@ -54,7 +54,6 @@ export type Safe4337InitOptions = { } options: ExistingSafeOptions | PredictedSafeOptions paymasterOptions?: PaymasterOptions - // on-chain analitics onchainAnalitics?: OnchainAnaliticsProps } @@ -67,7 +66,6 @@ export type Safe4337Options = { entryPointAddress: string safe4337ModuleAddress: string safeWebAuthnSharedSignerAddress?: string - // on-chain analitics onchainAnalitics?: OnchainAnaliticsProps } diff --git a/packages/relay-kit/src/packs/safe-4337/utils/getRelayKitVersion.ts b/packages/relay-kit/src/packs/safe-4337/utils/getRelayKitVersion.ts new file mode 100644 index 000000000..8f90e8f67 --- /dev/null +++ b/packages/relay-kit/src/packs/safe-4337/utils/getRelayKitVersion.ts @@ -0,0 +1,7 @@ +import packageJson from '../../../../package.json' + +function getRelayKitVersion(): string { + return packageJson.version +} + +export default getRelayKitVersion diff --git a/packages/relay-kit/tsconfig.build.json b/packages/relay-kit/tsconfig.build.json index cc22498f1..0dbe71055 100644 --- a/packages/relay-kit/tsconfig.build.json +++ b/packages/relay-kit/tsconfig.build.json @@ -4,6 +4,6 @@ "composite": true, "outDir": "dist" }, - "include": ["src/**/*"], + "include": ["src/**/*", "package.json"], "exclude": ["src/**/*.test.ts", "src/**/*.test-d.ts"] } diff --git a/packages/relay-kit/tsconfig.json b/packages/relay-kit/tsconfig.json index c05e497c4..51551a680 100644 --- a/packages/relay-kit/tsconfig.json +++ b/packages/relay-kit/tsconfig.json @@ -4,5 +4,5 @@ "composite": true, "outDir": "dist" }, - "include": ["src/**/*"] + "include": ["src/**/*", "package.json"] } diff --git a/playground/protocol-kit/deploy-safe.ts b/playground/protocol-kit/deploy-safe.ts index 4db8f9427..eadbf568b 100644 --- a/playground/protocol-kit/deploy-safe.ts +++ b/playground/protocol-kit/deploy-safe.ts @@ -1,4 +1,3 @@ -import * as dotenv from 'dotenv' import Safe, { SafeAccountConfig, getSafeAddressFromDeploymentTx } from '@safe-global/protocol-kit' import { SafeVersion } from '@safe-global/types-kit' @@ -8,10 +7,6 @@ import { sepolia } from 'viem/chains' import { waitForTransactionReceipt } from 'viem/actions' import semverSatisfies from 'semver/functions/satisfies' -dotenv.config() - -const { SIGNER_ADDRESS_PRIVATE_KEY } = process.env - // This file can be used to play around with the Safe Core SDK interface Config { @@ -27,7 +22,7 @@ interface Config { const config: Config = { RPC_URL: sepolia.rpcUrls.default.http[0], - DEPLOYER_ADDRESS_PRIVATE_KEY: SIGNER_ADDRESS_PRIVATE_KEY!, + DEPLOYER_ADDRESS_PRIVATE_KEY: '', DEPLOY_SAFE: { OWNERS: ['OWNER_ADDRESS'], THRESHOLD: 1, // From b204e9e57464164e3f7aea2702f7326d8e834068 Mon Sep 17 00:00:00 2001 From: Daniel Somoza Date: Wed, 11 Dec 2024 15:38:45 +0100 Subject: [PATCH 09/12] fix analytics typo --- packages/protocol-kit/src/Safe.ts | 10 +++++----- packages/protocol-kit/src/types/safeConfig.ts | 10 +++++----- .../tests/e2e/onChainIdentifier.test.ts | 14 +++++++------- .../relay-kit/src/packs/safe-4337/Safe4337Pack.ts | 12 ++++++------ packages/relay-kit/src/packs/safe-4337/types.ts | 6 +++--- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/protocol-kit/src/Safe.ts b/packages/protocol-kit/src/Safe.ts index 2a1c78d38..c578ac8af 100644 --- a/packages/protocol-kit/src/Safe.ts +++ b/packages/protocol-kit/src/Safe.ts @@ -105,7 +105,7 @@ class Safe { #MAGIC_VALUE = '0x1626ba7e' #MAGIC_VALUE_BYTES = '0x20c13b0b' - // on-chain Analitics + // on-chain Analytics #onchainIdentifier: string = '' /** @@ -132,10 +132,10 @@ class Safe { * @throws "MultiSendCallOnly contract is not deployed on the current network" */ async #initializeProtocolKit(config: SafeConfig) { - const { provider, signer, isL1SafeSingleton, contractNetworks, onchainAnalitics } = config + const { provider, signer, isL1SafeSingleton, contractNetworks, onchainAnalytics } = config - if (onchainAnalitics?.project) { - const { project, platform } = onchainAnalitics + if (onchainAnalytics?.project) { + const { project, platform } = onchainAnalytics this.#onchainIdentifier = generateOnChainIdentifier({ project, platform, @@ -1539,7 +1539,7 @@ class Safe { const safeDeploymentBatch = await this.createTransactionBatch( transactions, transactionOptions, - true // include the on chain identifier + !!this.#onchainIdentifier // include the on chain identifier ) return safeDeploymentBatch diff --git a/packages/protocol-kit/src/types/safeConfig.ts b/packages/protocol-kit/src/types/safeConfig.ts index 7b178b7dd..7c330ceaf 100644 --- a/packages/protocol-kit/src/types/safeConfig.ts +++ b/packages/protocol-kit/src/types/safeConfig.ts @@ -41,7 +41,7 @@ type SafeConfigWithPredictedSafeProps = { predictedSafe: PredictedSafeProps } -export type OnchainAnaliticsProps = { +export type OnchainAnalyticsProps = { /** project - The project that is using the SDK */ project?: string /** platform - The platform that is using the SDK */ @@ -55,8 +55,8 @@ export type SafeConfigProps = { isL1SafeSingleton?: boolean /** contractNetworks - Contract network configuration */ contractNetworks?: ContractNetworksConfig - // on-chain analitics - onchainAnalitics?: OnchainAnaliticsProps + // on-chain analytics + onchainAnalytics?: OnchainAnalyticsProps } export type SafeConfigWithSafeAddress = SafeConfigProps & SafeConfigWithSafeAddressProps @@ -84,8 +84,8 @@ type ConnectSafeConfigProps = { isL1SafeSingleton?: boolean /** contractNetworks - Contract network configuration */ contractNetworks?: ContractNetworksConfig - // on-chain analitics - onchainAnalitics?: OnchainAnaliticsProps + // on-chain analytics + onchainAnalytics?: OnchainAnalyticsProps } export type ConnectSafeConfigWithSafeAddress = ConnectSafeConfigProps & diff --git a/packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts b/packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts index a4d0eadda..73043544a 100644 --- a/packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts +++ b/packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts @@ -1,7 +1,7 @@ import { setupTests, safeVersionDeployed } from '@safe-global/testing-kit' import Safe, { generateOnChainIdentifier, - OnchainAnaliticsProps, + OnchainAnalyticsProps, PredictedSafeProps, SafeAccountConfig } from '@safe-global/protocol-kit/index' @@ -40,7 +40,7 @@ describe('On-chain analytics', () => { describe('getOnchainIdentifier method', () => { it('should return the on-chain identifier when provided', async () => { - const onchainAnalitics: OnchainAnaliticsProps = { + const onchainAnalytics: OnchainAnalyticsProps = { project: 'Test e2e Project', platform: 'Web' } @@ -52,7 +52,7 @@ describe('On-chain analytics', () => { provider, safeAddress, contractNetworks, - onchainAnalitics + onchainAnalytics }) const onChainIdentifier = '5afe003861653435366632366138366164643038373864646561393238653366' @@ -76,7 +76,7 @@ describe('On-chain analytics', () => { describe('Tracking Safe Deployment on-chain via the transaction data field', () => { it('should append the on-chain identifier to the deployment transaction data field', async () => { - const onchainAnalitics: OnchainAnaliticsProps = { + const onchainAnalytics: OnchainAnalyticsProps = { project: 'Test e2e Project', platform: 'Web' } @@ -97,7 +97,7 @@ describe('On-chain analytics', () => { provider, predictedSafe, contractNetworks, - onchainAnalitics + onchainAnalytics }) const deploymentTransaction = await protocolKit.createSafeDeploymentTransaction() @@ -111,7 +111,7 @@ describe('On-chain analytics', () => { describe('Tracking Safe transactions on-chain via the transaction data field', () => { it('should append the on-chain identifier to the execTransaction data field', async () => { - const onchainAnalitics: OnchainAnaliticsProps = { + const onchainAnalytics: OnchainAnalyticsProps = { project: 'Test e2e Project', platform: 'Web' } @@ -123,7 +123,7 @@ describe('On-chain analytics', () => { provider, safeAddress, contractNetworks, - onchainAnalitics + onchainAnalytics }) const testTransaction = { diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index df55fd2ac..e45507f4a 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -105,7 +105,7 @@ export class Safe4337Pack extends RelayKitBasePack<{ entryPointAddress, safe4337ModuleAddress, safeWebAuthnSharedSignerAddress, - onchainAnalitics + onchainAnalytics }: Safe4337Options) { super(protocolKit) @@ -117,8 +117,8 @@ export class Safe4337Pack extends RelayKitBasePack<{ this.#SAFE_4337_MODULE_ADDRESS = safe4337ModuleAddress this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS = safeWebAuthnSharedSignerAddress || '0x' - if (onchainAnalitics?.project) { - const { project, platform } = onchainAnalitics + if (onchainAnalytics?.project) { + const { project, platform } = onchainAnalytics this.#onchainIdentifier = generateOnChainIdentifier({ project, platform, @@ -146,7 +146,7 @@ export class Safe4337Pack extends RelayKitBasePack<{ bundlerUrl, customContracts, paymasterOptions, - onchainAnalitics + onchainAnalytics } = initOptions let protocolKit: Safe @@ -354,7 +354,7 @@ export class Safe4337Pack extends RelayKitBasePack<{ paymentReceiver: zeroAddress } }, - onchainAnalitics + onchainAnalytics }) } @@ -398,7 +398,7 @@ export class Safe4337Pack extends RelayKitBasePack<{ entryPointAddress: selectedEntryPoint!, safe4337ModuleAddress, safeWebAuthnSharedSignerAddress, - onchainAnalitics + onchainAnalytics }) } diff --git a/packages/relay-kit/src/packs/safe-4337/types.ts b/packages/relay-kit/src/packs/safe-4337/types.ts index ea555e17f..77ba49ae0 100644 --- a/packages/relay-kit/src/packs/safe-4337/types.ts +++ b/packages/relay-kit/src/packs/safe-4337/types.ts @@ -2,7 +2,7 @@ import { Account, Address, Chain, Hash, Hex, PublicClient, PublicRpcSchema, Tran import Safe, { DeploymentType, SafeProviderConfig, - OnchainAnaliticsProps + OnchainAnalyticsProps } from '@safe-global/protocol-kit' import { EstimateGasData, @@ -54,7 +54,7 @@ export type Safe4337InitOptions = { } options: ExistingSafeOptions | PredictedSafeOptions paymasterOptions?: PaymasterOptions - onchainAnalitics?: OnchainAnaliticsProps + onchainAnalytics?: OnchainAnalyticsProps } export type Safe4337Options = { @@ -66,7 +66,7 @@ export type Safe4337Options = { entryPointAddress: string safe4337ModuleAddress: string safeWebAuthnSharedSignerAddress?: string - onchainAnalitics?: OnchainAnaliticsProps + onchainAnalytics?: OnchainAnalyticsProps } export type Safe4337CreateTransactionProps = { From 54c71df0dcf587565b9219350b8dacff08fbafa2 Mon Sep 17 00:00:00 2001 From: Daniel Somoza Date: Wed, 11 Dec 2024 15:53:03 +0100 Subject: [PATCH 10/12] added "resolveJsonModule": true in tsconfig --- packages/protocol-kit/tsconfig.build.json | 1 + packages/protocol-kit/tsconfig.json | 1 + packages/relay-kit/tsconfig.build.json | 1 + packages/relay-kit/tsconfig.json | 1 + 4 files changed, 4 insertions(+) diff --git a/packages/protocol-kit/tsconfig.build.json b/packages/protocol-kit/tsconfig.build.json index 51551a680..416bcce06 100644 --- a/packages/protocol-kit/tsconfig.build.json +++ b/packages/protocol-kit/tsconfig.build.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.settings.json", "compilerOptions": { + "resolveJsonModule": true, "composite": true, "outDir": "dist" }, diff --git a/packages/protocol-kit/tsconfig.json b/packages/protocol-kit/tsconfig.json index fc2fc6868..77c78ede6 100644 --- a/packages/protocol-kit/tsconfig.json +++ b/packages/protocol-kit/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.settings.json", "compilerOptions": { + "resolveJsonModule": true, "composite": true, "outDir": "dist" }, diff --git a/packages/relay-kit/tsconfig.build.json b/packages/relay-kit/tsconfig.build.json index 0dbe71055..9c287382b 100644 --- a/packages/relay-kit/tsconfig.build.json +++ b/packages/relay-kit/tsconfig.build.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.settings.json", "compilerOptions": { + "resolveJsonModule": true, "composite": true, "outDir": "dist" }, diff --git a/packages/relay-kit/tsconfig.json b/packages/relay-kit/tsconfig.json index 51551a680..416bcce06 100644 --- a/packages/relay-kit/tsconfig.json +++ b/packages/relay-kit/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.settings.json", "compilerOptions": { + "resolveJsonModule": true, "composite": true, "outDir": "dist" }, From 342814a6a039a8b235387299c5c4eb4de0789ad0 Mon Sep 17 00:00:00 2001 From: Daniel Somoza Date: Wed, 11 Dec 2024 16:02:56 +0100 Subject: [PATCH 11/12] added log to identify error in the pipeline --- .../src/utils/on-chain-tracking/generateOnChainIdentifier.ts | 2 ++ packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts b/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts index aee82d29f..433f6a865 100644 --- a/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts +++ b/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts @@ -44,6 +44,8 @@ function generateOnChainIdentifier({ tool, toolVersion }: OnChainIdentifierParamsType): string { + console.log('@@@ toolVersion: ', toolVersion) + const identifierPrefix = '5afe' const identifierVersion = '00' // first version const projectHash = generateHash(project, 20) // Take the last 20 bytes diff --git a/packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts b/packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts index 73043544a..269b034df 100644 --- a/packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts +++ b/packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts @@ -38,7 +38,7 @@ describe('On-chain analytics', () => { }) }) - describe('getOnchainIdentifier method', () => { + describe.only('getOnchainIdentifier method', () => { it('should return the on-chain identifier when provided', async () => { const onchainAnalytics: OnchainAnalyticsProps = { project: 'Test e2e Project', From 154ce85b217b8d738fde50a4f61c4bc02d582a66 Mon Sep 17 00:00:00 2001 From: Daniel Somoza Date: Wed, 11 Dec 2024 16:29:54 +0100 Subject: [PATCH 12/12] fix toolVersion test issue --- .../generateOnChainIdentifier.ts | 2 -- .../tests/e2e/onChainIdentifier.test.ts | 23 +++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts b/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts index 433f6a865..aee82d29f 100644 --- a/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts +++ b/packages/protocol-kit/src/utils/on-chain-tracking/generateOnChainIdentifier.ts @@ -44,8 +44,6 @@ function generateOnChainIdentifier({ tool, toolVersion }: OnChainIdentifierParamsType): string { - console.log('@@@ toolVersion: ', toolVersion) - const identifierPrefix = '5afe' const identifierVersion = '00' // first version const projectHash = generateHash(project, 20) // Take the last 20 bytes diff --git a/packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts b/packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts index 269b034df..b45f47769 100644 --- a/packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts +++ b/packages/protocol-kit/tests/e2e/onChainIdentifier.test.ts @@ -6,11 +6,13 @@ import Safe, { SafeAccountConfig } from '@safe-global/protocol-kit/index' import chai from 'chai' +import Sinon from 'sinon' import chaiAsPromised from 'chai-as-promised' +import { generateHash } from '@safe-global/protocol-kit/utils/on-chain-tracking/generateOnChainIdentifier' +import getProtocolKitVersion, * as getProtocolKitVersionModule from '@safe-global/protocol-kit/utils/getProtocolKitVersion' import { getEip1193Provider } from './utils/setupProvider' import { waitSafeTxReceipt } from './utils/transactions' -import { generateHash } from '@safe-global/protocol-kit/utils/on-chain-tracking/generateOnChainIdentifier' chai.use(chaiAsPromised) @@ -38,13 +40,15 @@ describe('On-chain analytics', () => { }) }) - describe.only('getOnchainIdentifier method', () => { + describe('getOnchainIdentifier method', () => { it('should return the on-chain identifier when provided', async () => { const onchainAnalytics: OnchainAnalyticsProps = { project: 'Test e2e Project', platform: 'Web' } + const stub = Sinon.stub(getProtocolKitVersionModule, 'default').returns('5.0.4') + const { safe, contractNetworks } = await setupTests() const safeAddress = safe.address @@ -58,6 +62,7 @@ describe('On-chain analytics', () => { const onChainIdentifier = '5afe003861653435366632366138366164643038373864646561393238653366' chai.expect(onChainIdentifier).to.equals(protocolKit.getOnchainIdentifier()) + stub.restore() }) it('should return an empty string when no onchain Analiticts is provided', async () => { @@ -102,7 +107,12 @@ describe('On-chain analytics', () => { const deploymentTransaction = await protocolKit.createSafeDeploymentTransaction() - const onChainIdentifier = '5afe003861653435366632366138366164643038373864646561393238653366' + // toolVersion is dynamic (currrent protocol-kit version) + const toolVersion = getProtocolKitVersion() + const toolHash = generateHash(toolVersion, 3) + + const onChainIdentifier = + '5afe003861653435366632366138366164643038373864646561393238' + toolHash chai.expect(onChainIdentifier).to.equals(protocolKit.getOnchainIdentifier()) chai.expect(deploymentTransaction.data.endsWith(onChainIdentifier)).to.be.true @@ -147,7 +157,12 @@ describe('On-chain analytics', () => { .getSafeProvider() .getTransaction(transactionResponse.hash) - const onChainIdentifier = '5afe003861653435366632366138366164643038373864646561393238653366' + // toolVersion is dynamic (currrent protocol-kit version) + const toolVersion = getProtocolKitVersion() + const toolHash = generateHash(toolVersion, 3) + + const onChainIdentifier = + '5afe003861653435366632366138366164643038373864646561393238' + toolHash chai.expect(onChainIdentifier).to.equals(protocolKit.getOnchainIdentifier()) chai.expect(transaction.input.endsWith(onChainIdentifier)).to.be.true