feat: add Safe Transaction Builder to Warp Apply (#4621)

### Description
Adds feature to create a Safe Transaction Builder "receipt" that can be
uploaded to the Safe UI.
This relies on a a gnosisSafeTxBuilder strategy:
```
basesepolia:
  submitter:
    chain: 'basesepolia'
    type: gnosisSafeTxBuilder
    version: '1.0'
    meta: {}
    safeAddress: '0x7232Ad76d905ae9D8D00379359DDa744a7A21C46'

```

To generate this, to be uploaded to the UI:
```
{
  "version": "1.0",
  "chainId": "10200",
  "meta": {},
  "transactions": [
    {
      "to": "0xB86F6AF56C411688b3dAB479f646E990287094a0",
      "value": "0",
      "data": "0xe9198bf9000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000014a3400000000000000000000000000000000000000000000000000000000002fd16700000000000000000000000000000000000000000000000000000000002fd1cb000000000000000000000000000000000000000000000000000000000000000300000000000000000000000021d4928020f503658603a036dd3ad5570e8495ab000000000000000000000000de612cfd22a35aae677398decd7a13057d6d5a300000000000000000000000008c08821f5f94b519c853486eb131667aa528a460",
      "operation": 0,
      "baseGas": "0",
      "gasPrice": "0",
      "gasToken": "0x0000000000000000000000000000000000000000",
      "refundReceiver": "0x0000000000000000000000000000000000000000",
      "nonce": 9,
      "safeTxGas": "0"
    }
  ]
}

```

<img width="1713" alt="Screenshot 2024-10-03 at 1 08 31 PM"
src="https://github.com/user-attachments/assets/60009b47-4018-455b-8324-495f286de973">



### Drive-by changes
- Refactor warp apply to use `submitWarpApplyTransactions()` to submit
and write the receipt file, de-duplicating code.

### Related issues
- Fixes #4620

### Backward compatibility
Yes

### Testing
Manual
- [ ] Enrollment on 1 chain basesepolia
  - [ ] adding 2 anvil chains
- [ ] Enrollment to 2 chains: basesepolia chiado
  - [ ] Adding 2 anvil chains
pull/4640/head
Lee 2 months ago committed by GitHub
parent bb75eba74a
commit 4415ac224a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      .changeset/loud-glasses-beg.md
  2. 6
      typescript/cli/examples/submit/strategy/gnosis-chain-tx-builder-strategy.yaml
  3. 23
      typescript/cli/src/commands/warp.ts
  4. 127
      typescript/cli/src/deploy/warp.ts
  5. 5
      typescript/cli/src/submit/submit.ts
  6. 7
      typescript/cli/src/utils/files.ts
  7. 2
      typescript/sdk/src/consts/concurrency.ts
  8. 1
      typescript/sdk/src/index.ts
  9. 22
      typescript/sdk/src/providers/ProviderType.ts
  10. 2
      typescript/sdk/src/providers/providerBuilders.ts
  11. 6
      typescript/sdk/src/providers/transactions/submitter/TxSubmitterInterface.ts
  12. 1
      typescript/sdk/src/providers/transactions/submitter/TxSubmitterTypes.ts
  13. 6
      typescript/sdk/src/providers/transactions/submitter/builder/TxSubmitterBuilder.ts
  14. 72
      typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxBuilder.ts
  15. 51
      typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts
  16. 6
      typescript/sdk/src/providers/transactions/submitter/ethersV5/schemas.ts
  17. 4
      typescript/sdk/src/providers/transactions/submitter/ethersV5/types.ts
  18. 5
      typescript/sdk/src/providers/transactions/submitter/schemas.ts
  19. 1
      typescript/sdk/src/token/TokenStandard.ts
  20. 1
      typescript/utils/src/types.ts

@ -0,0 +1,7 @@
---
'@hyperlane-xyz/utils': minor
'@hyperlane-xyz/cli': minor
'@hyperlane-xyz/sdk': minor
---
Add Gnosis safe transaction builder to warp apply

@ -0,0 +1,6 @@
basesepolia:
submitter:
chain: 'basesepolia'
type: gnosisSafeTxBuilder
version: '1.0'
safeAddress: '0x7232Ad76d905ae9D8D00379359DDa744a7A21C46'

@ -28,7 +28,11 @@ import { evaluateIfDryRunFailure } from '../deploy/dry-run.js';
import { runWarpRouteApply, runWarpRouteDeploy } from '../deploy/warp.js';
import { log, logGray, logGreen, logRed, logTable } from '../logger.js';
import { sendTestTransfer } from '../send/transfer.js';
import { indentYamlOrJson, writeYamlOrJson } from '../utils/files.js';
import {
indentYamlOrJson,
removeEndingSlash,
writeYamlOrJson,
} from '../utils/files.js';
import { selectRegistryWarpRoute } from '../utils/tokens.js';
import {
@ -68,6 +72,7 @@ export const apply: CommandModuleWithWriteContext<{
symbol?: string;
warp: string;
strategy?: string;
receiptsDir: string;
}> = {
command: 'apply',
describe: 'Update Warp Route contracts',
@ -82,8 +87,21 @@ export const apply: CommandModuleWithWriteContext<{
demandOption: false,
},
strategy: { ...strategyCommandOption, demandOption: false },
'receipts-dir': {
type: 'string',
description: 'The directory to output transaction receipts.',
default: './generated/transactions',
coerce: (dir) => removeEndingSlash(dir),
},
},
handler: async ({ context, config, symbol, warp, strategy: strategyUrl }) => {
handler: async ({
context,
config,
symbol,
warp,
strategy: strategyUrl,
receiptsDir,
}) => {
logGray(`Hyperlane Warp Apply`);
logGray('--------------------'); // @TODO consider creating a helper function for these dashes
let warpCoreConfig: WarpCoreConfig;
@ -102,6 +120,7 @@ export const apply: CommandModuleWithWriteContext<{
warpDeployConfig,
warpCoreConfig,
strategyUrl,
receiptsDir,
});
process.exit(0);
},

@ -57,6 +57,7 @@ import {
objKeys,
objMap,
promiseObjAll,
retryAsync,
} from '@hyperlane-xyz/utils';
import { readWarpRouteDeployConfig } from '../config/warp.js';
@ -77,6 +78,7 @@ import {
isFile,
readYamlOrJson,
runFileSelectionStep,
writeYamlOrJson,
} from '../utils/files.js';
import {
@ -93,6 +95,7 @@ interface DeployParams {
interface WarpApplyParams extends DeployParams {
warpCoreConfig: WarpCoreConfig;
strategyUrl?: string;
receiptsDir: string;
}
export async function runWarpRouteDeploy({
@ -434,7 +437,7 @@ function fullyConnectTokens(warpCoreConfig: WarpCoreConfig): void {
export async function runWarpRouteApply(
params: WarpApplyParams,
): Promise<void> {
const { warpDeployConfig, warpCoreConfig, context, strategyUrl } = params;
const { warpDeployConfig, warpCoreConfig, context } = params;
const { registry, multiProvider, chainMetadata, skipConfirmation } = context;
WarpRouteDeployConfigSchema.parse(warpDeployConfig);
@ -489,18 +492,7 @@ export async function runWarpRouteApply(
return logGreen(
`Warp config on ${chain} is the same as target. No updates needed.`,
);
const submitter: TxSubmitterBuilder<ProtocolType> =
await getWarpApplySubmitter({
chain,
context,
strategyUrl,
});
const transactionReceipts = await submitter.submit(...transactions);
return logGreen(
`✅ Warp config update successfully submitted with ${submitter.txSubmitterType} on ${chain}:\n\n`,
indentYamlOrJson(yamlStringify(transactionReceipts, null, 2), 4),
);
await submitWarpApplyTransactions(chain, params, transactions);
} catch (e) {
logRed(`Warp config on ${chain} failed to update.`, e);
}
@ -542,7 +534,7 @@ export async function runWarpRouteApply(
warpCoreConfigByChain,
);
await enrollRemoteRouters(context, mergedRouters, strategyUrl);
await enrollRemoteRouters(params, mergedRouters);
const updatedWarpCoreConfig = await getWarpCoreConfig(
params,
@ -623,62 +615,53 @@ function mergeAllRouters(
* @param multiProvider - A MultiProvider instance to interact with multiple chains.
*/
async function enrollRemoteRouters(
context: WriteCommandContext,
params: WarpApplyParams,
deployedContractsMap: HyperlaneContractsMap<HypERC20Factories>,
strategyUrl?: string,
): Promise<void> {
logBlue(`Enrolling deployed routers with each other (if not already)...`);
const { multiProvider } = context;
logBlue(`Enrolling deployed routers with each other...`);
const { multiProvider } = params.context;
const deployedRouters: ChainMap<Address> = objMap(
deployedContractsMap,
(_, contracts) => getRouter(contracts).address,
);
const allChains = Object.keys(deployedRouters);
await promiseObjAll(
objMap(deployedContractsMap, async (chain, contracts) => {
const router = getRouter(contracts); // Assume deployedContract always has 1 value
// Mutate the config.remoteRouters by setting it to all other routers to update
const warpRouteReader = new EvmERC20WarpRouteReader(multiProvider, chain);
const mutatedWarpRouteConfig =
await warpRouteReader.deriveWarpRouteConfig(router.address);
const evmERC20WarpModule = new EvmERC20WarpModule(multiProvider, {
config: mutatedWarpRouteConfig,
chain,
addresses: { deployedTokenRoute: router.address },
});
await retryAsync(async () => {
const router = getRouter(contracts); // Assume deployedContract always has 1 value
const otherChains = multiProvider
.getRemoteChains(chain)
.filter((c) => allChains.includes(c));
mutatedWarpRouteConfig.remoteRouters = otherChains.reduce<RemoteRouters>(
(remoteRouters, chain) => {
remoteRouters[multiProvider.getDomainId(chain)] =
deployedRouters[chain];
return remoteRouters;
},
{},
);
const mutatedConfigTxs: AnnotatedEV5Transaction[] =
await evmERC20WarpModule.update(mutatedWarpRouteConfig);
if (mutatedConfigTxs.length == 0)
return logGreen(
`Mutated warp config on ${chain} is the same as target. No updates needed.`,
// Mutate the config.remoteRouters by setting it to all other routers to update
const warpRouteReader = new EvmERC20WarpRouteReader(
multiProvider,
chain,
);
const submitter: TxSubmitterBuilder<ProtocolType> =
await getWarpApplySubmitter({
const mutatedWarpRouteConfig =
await warpRouteReader.deriveWarpRouteConfig(router.address);
const evmERC20WarpModule = new EvmERC20WarpModule(multiProvider, {
config: mutatedWarpRouteConfig,
chain,
context,
strategyUrl,
addresses: { deployedTokenRoute: router.address },
});
const transactionReceipts = await submitter.submit(...mutatedConfigTxs);
return logGreen(
`✅ Router enrollment update successfully submitted with ${submitter.txSubmitterType} on ${chain}:\n\n`,
indentYamlOrJson(yamlStringify(transactionReceipts, null, 2), 4),
);
const otherChains = multiProvider
.getRemoteChains(chain)
.filter((c) => allChains.includes(c));
mutatedWarpRouteConfig.remoteRouters =
otherChains.reduce<RemoteRouters>((remoteRouters, chain) => {
remoteRouters[multiProvider.getDomainId(chain)] =
deployedRouters[chain];
return remoteRouters;
}, {});
const mutatedConfigTxs: AnnotatedEV5Transaction[] =
await evmERC20WarpModule.update(mutatedWarpRouteConfig);
if (mutatedConfigTxs.length == 0)
return logGreen(
`Warp config on ${chain} is the same as target. No updates needed.`,
);
await submitWarpApplyTransactions(chain, params, mutatedConfigTxs);
});
}),
);
}
@ -818,6 +801,36 @@ function transformIsmConfigForDisplay(ismConfig: IsmConfig): any[] {
}
}
/**
* Submits a set of transactions to the specified chain and outputs transaction receipts
*/
async function submitWarpApplyTransactions(
chain: string,
params: WarpApplyParams,
transactions: AnnotatedEV5Transaction[],
) {
const submitter: TxSubmitterBuilder<ProtocolType> =
await getWarpApplySubmitter({
chain,
context: params.context,
strategyUrl: params.strategyUrl,
});
const transactionReceipts = await submitter.submit(...transactions);
if (transactionReceipts) {
const receiptPath = `${params.receiptsDir}/${chain}-${
submitter.txSubmitterType
}-${Date.now()}-receipts.json`;
writeYamlOrJson(receiptPath, transactionReceipts);
logGreen(`Transactions receipts successfully written to ${receiptPath}`);
}
return logGreen(
`✅ Warp route update success with ${submitter.txSubmitterType} on ${chain}:\n\n`,
indentYamlOrJson(yamlStringify(transactionReceipts, null, 2), 0),
);
}
/**
* Helper function to get warp apply specific submitter.
*

@ -1,4 +1,5 @@
import {
EV5GnosisSafeTxBuilder,
EV5GnosisSafeTxSubmitter,
EV5ImpersonatedAccountTxSubmitter,
EV5InterchainAccountTxTransformer,
@ -47,6 +48,10 @@ async function getSubmitter<TProtocol extends ProtocolType>(
return EV5GnosisSafeTxSubmitter.create(multiProvider, {
...submitterMetadata,
});
case TxSubmitterType.GNOSIS_TX_BUILDER:
return EV5GnosisSafeTxBuilder.create(multiProvider, {
...submitterMetadata,
});
default:
throw new Error(`Invalid TxSubmitterType.`);
}

@ -23,6 +23,13 @@ export type ArtifactsFile = {
description: string;
};
export function removeEndingSlash(dirPath: string): string {
if (dirPath.endsWith('/')) {
return dirPath.slice(0, -1);
}
return dirPath;
}
export function resolvePath(filePath: string): string {
if (filePath.startsWith('~')) {
const homedir = os.homedir();

@ -1 +1 @@
export const DEFAULT_CONTRACT_READ_CONCURRENCY = 20;
export const DEFAULT_CONTRACT_READ_CONCURRENCY = 1;

@ -347,6 +347,7 @@ export {
} from './providers/transactions/submitter/builder/types.js';
export { EV5GnosisSafeTxSubmitter } from './providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.js';
export { EV5GnosisSafeTxBuilder } from './providers/transactions/submitter/ethersV5/EV5GnosisSafeTxBuilder.js';
export { EV5ImpersonatedAccountTxSubmitter } from './providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.js';
export { EV5JsonRpcTxSubmitter } from './providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.js';
export { EV5TxSubmitterInterface } from './providers/transactions/submitter/ethersV5/EV5TxSubmitterInterface.js';

@ -5,6 +5,7 @@ import type {
} from '@cosmjs/cosmwasm-stargate';
import type { EncodeObject as CmTransaction } from '@cosmjs/proto-signing';
import type { DeliverTxResponse, StargateClient } from '@cosmjs/stargate';
import { SafeTransactionData } from '@safe-global/safe-core-sdk-types';
import type {
Connection,
Transaction as SolTransaction,
@ -30,6 +31,7 @@ export enum ProviderType {
SolanaWeb3 = 'solana-web3',
CosmJs = 'cosmjs',
CosmJsWasm = 'cosmjs-wasm',
GnosisTxBuilder = 'gnosis-txBuilder',
}
export const PROTOCOL_TO_DEFAULT_PROVIDER_TYPE: Record<
@ -37,6 +39,7 @@ export const PROTOCOL_TO_DEFAULT_PROVIDER_TYPE: Record<
ProviderType
> = {
[ProtocolType.Ethereum]: ProviderType.EthersV5,
[ProtocolType.GnosisTxBuilder]: ProviderType.EthersV5,
[ProtocolType.Sealevel]: ProviderType.SolanaWeb3,
[ProtocolType.Cosmos]: ProviderType.CosmJsWasm,
};
@ -50,6 +53,12 @@ type ProtocolTypesMapping = {
contract: EthersV5Contract;
receipt: EthersV5TransactionReceipt;
};
[ProtocolType.GnosisTxBuilder]: {
transaction: EthersV5Transaction;
provider: EthersV5Provider;
contract: EthersV5Contract;
receipt: GnosisTransactionBuilderReceipt;
};
[ProtocolType.Sealevel]: {
transaction: SolanaWeb3Transaction;
provider: SolanaWeb3Provider;
@ -258,6 +267,19 @@ export interface CosmJsTransactionReceipt
receipt: DeliverTxResponse;
}
export interface GnosisTransactionBuilderReceipt
extends TypedTransactionReceiptBase<GnosisTransactionBuilderPayload> {
type: ProviderType.GnosisTxBuilder;
receipt: GnosisTransactionBuilderPayload;
}
export interface GnosisTransactionBuilderPayload {
version: string;
chainId: string;
meta: {};
transactions: SafeTransactionData[];
}
export interface CosmJsWasmTransactionReceipt
extends TypedTransactionReceiptBase<DeliverTxResponse> {
type: ProviderType.CosmJsWasm;

@ -123,6 +123,7 @@ export type ProviderBuilderMap = Record<
>;
export const defaultProviderBuilderMap: ProviderBuilderMap = {
[ProviderType.EthersV5]: defaultEthersV5ProviderBuilder,
[ProviderType.GnosisTxBuilder]: defaultEthersV5ProviderBuilder,
[ProviderType.Viem]: defaultViemProviderBuilder,
[ProviderType.SolanaWeb3]: defaultSolProviderBuilder,
[ProviderType.CosmJs]: defaultCosmJsProviderBuilder,
@ -134,6 +135,7 @@ export const protocolToDefaultProviderBuilder: Record<
ProviderBuilderFn<TypedProvider>
> = {
[ProtocolType.Ethereum]: defaultEthersV5ProviderBuilder,
[ProtocolType.GnosisTxBuilder]: defaultEthersV5ProviderBuilder,
[ProtocolType.Sealevel]: defaultSolProviderBuilder,
[ProtocolType.Cosmos]: defaultCosmJsWasmProviderBuilder,
};

@ -23,5 +23,9 @@ export interface TxSubmitterInterface<TProtocol extends ProtocolType> {
*/
submit(
...txs: ProtocolTypedTransaction<TProtocol>['transaction'][]
): Promise<ProtocolTypedReceipt<TProtocol>['receipt'][] | void>;
): Promise<
| ProtocolTypedReceipt<TProtocol>['receipt']
| ProtocolTypedReceipt<TProtocol>['receipt'][]
| void
>;
}

@ -2,4 +2,5 @@ export enum TxSubmitterType {
JSON_RPC = 'jsonRpc',
IMPERSONATED_ACCOUNT = 'impersonatedAccount',
GNOSIS_SAFE = 'gnosisSafe',
GNOSIS_TX_BUILDER = 'gnosisSafeTxBuilder',
}

@ -74,7 +74,11 @@ export class TxSubmitterBuilder<TProtocol extends ProtocolType>
*/
public async submit(
...txs: ProtocolTypedTransaction<TProtocol>['transaction'][]
): Promise<ProtocolTypedReceipt<TProtocol>['receipt'][] | void> {
): Promise<
| ProtocolTypedReceipt<TProtocol>['receipt']
| ProtocolTypedReceipt<TProtocol>['receipt'][]
| void
> {
this.logger.debug(
`Submitting ${txs.length} transactions to the ${this.currentSubmitter.txSubmitterType} submitter...`,
);

@ -0,0 +1,72 @@
import { SafeTransactionData } from '@safe-global/safe-core-sdk-types';
import { assert } from '@hyperlane-xyz/utils';
// prettier-ignore
// @ts-ignore
import { getSafe, getSafeService } from '../../../../utils/gnosisSafe.js';
import { MultiProvider } from '../../../MultiProvider.js';
import { GnosisTransactionBuilderPayload } from '../../../ProviderType.js';
import { PopulatedTransaction, PopulatedTransactions } from '../../types.js';
import { TxSubmitterType } from '../TxSubmitterTypes.js';
import { EV5GnosisSafeTxSubmitter } from './EV5GnosisSafeTxSubmitter.js';
import { EV5GnosisSafeTxBuilderProps } from './types.js';
/**
* This class is used to create a Safe Transaction Builder compatible object.
* It is not a true Submitter because it does not submits any transactions.
*/
export class EV5GnosisSafeTxBuilder extends EV5GnosisSafeTxSubmitter {
public readonly txSubmitterType: TxSubmitterType =
TxSubmitterType.GNOSIS_TX_BUILDER;
constructor(
public readonly multiProvider: MultiProvider,
public readonly props: EV5GnosisSafeTxBuilderProps,
safe: any,
safeService: any,
) {
super(multiProvider, props, safe, safeService);
}
static async create(
multiProvider: MultiProvider,
props: EV5GnosisSafeTxBuilderProps,
): Promise<EV5GnosisSafeTxBuilder> {
const { chain, safeAddress } = props;
const { gnosisSafeTransactionServiceUrl } =
multiProvider.getChainMetadata(chain);
assert(
gnosisSafeTransactionServiceUrl,
`Must set gnosisSafeTransactionServiceUrl in the Registry metadata for ${chain}`,
);
const safe = await getSafe(chain, multiProvider, safeAddress);
const safeService = await getSafeService(chain, multiProvider);
return new EV5GnosisSafeTxBuilder(multiProvider, props, safe, safeService);
}
/**
* Creates a Gnosis Safe transaction builder object using the PopulatedTransactions
*
* @param txs - An array of populated transactions
*/
public async submit(
...txs: PopulatedTransactions
): Promise<GnosisTransactionBuilderPayload> {
const transactions: SafeTransactionData[] = await Promise.all(
txs.map(
async (tx: PopulatedTransaction) =>
(
await this.createSafeTransaction(tx)
).data,
),
);
return {
version: this.props.version,
chainId: this.multiProvider.getChainId(this.props.chain).toString(),
meta: {},
transactions,
};
}
}

@ -1,3 +1,4 @@
import { SafeTransaction } from '@safe-global/safe-core-sdk-types';
import { Logger } from 'pino';
import { Address, assert, rootLogger } from '@hyperlane-xyz/utils';
@ -62,25 +63,43 @@ export class EV5GnosisSafeTxSubmitter implements EV5TxSubmitterInterface {
);
}
public async submit(...txs: PopulatedTransactions): Promise<any[]> {
public async createSafeTransaction({
to,
data,
value,
chainId,
}: PopulatedTransaction): Promise<SafeTransaction> {
const nextNonce: number = await this.safeService.getNextNonce(
this.props.safeAddress,
);
const safeTransactionBatch: any[] = txs.map(
({ to, data, value, chainId }: PopulatedTransaction) => {
const txChain = this.multiProvider.getChainName(chainId);
assert(
txChain === this.props.chain,
`Invalid PopulatedTransaction: Cannot submit ${txChain} tx to ${this.props.chain} submitter.`,
);
return { to, data, value: value?.toString() ?? '0' };
},
const txChain = this.multiProvider.getChainName(chainId);
assert(
txChain === this.props.chain,
`Invalid PopulatedTransaction: Cannot submit ${txChain} tx to ${this.props.chain} submitter.`,
);
const safeTransaction = await this.safe.createTransaction({
safeTransactionData: safeTransactionBatch,
return this.safe.createTransaction({
safeTransactionData: [{ to, data, value: value?.toString() ?? '0' }],
options: { nonce: nextNonce },
});
const safeTransactionData: any = safeTransaction.data;
}
public async submit(...txs: PopulatedTransactions): Promise<any> {
return this.proposeIndividualTransactions(txs);
}
private async proposeIndividualTransactions(txs: PopulatedTransactions) {
const safeTransactions: SafeTransaction[] = [];
for (const tx of txs) {
const safeTransaction = await this.createSafeTransaction(tx);
await this.proposeSafeTransaction(safeTransaction);
safeTransactions.push(safeTransaction);
}
return safeTransactions;
}
private async proposeSafeTransaction(
safeTransaction: SafeTransaction,
): Promise<void> {
const safeTxHash: string = await this.safe.getTransactionHash(
safeTransaction,
);
@ -94,14 +113,12 @@ export class EV5GnosisSafeTxSubmitter implements EV5TxSubmitterInterface {
`Submitting transaction proposal to ${this.props.safeAddress} on ${this.props.chain}: ${safeTxHash}`,
);
const transactionReceipts = await this.safeService.proposeTransaction({
return this.safeService.proposeTransaction({
safeAddress: this.props.safeAddress,
safeTransactionData,
safeTransactionData: safeTransaction.data,
safeTxHash,
senderAddress,
senderSignature,
});
return transactionReceipts ?? [];
}
}

@ -7,6 +7,12 @@ export const EV5GnosisSafeTxSubmitterPropsSchema = z.object({
safeAddress: ZHash,
});
export const EV5GnosisSafeTxBuilderPropsSchema = z.object({
version: z.string().default('1.0'),
chain: ZChainName,
safeAddress: ZHash,
});
export const EV5ImpersonatedAccountTxSubmitterPropsSchema = z.object({
userAddress: ZHash,
});

@ -1,6 +1,7 @@
import { z } from 'zod';
import {
EV5GnosisSafeTxBuilderPropsSchema,
EV5GnosisSafeTxSubmitterPropsSchema,
EV5ImpersonatedAccountTxSubmitterPropsSchema,
} from './schemas.js';
@ -8,6 +9,9 @@ import {
export type EV5GnosisSafeTxSubmitterProps = z.infer<
typeof EV5GnosisSafeTxSubmitterPropsSchema
>;
export type EV5GnosisSafeTxBuilderProps = z.infer<
typeof EV5GnosisSafeTxBuilderPropsSchema
>;
export type EV5ImpersonatedAccountTxSubmitterProps = z.infer<
typeof EV5ImpersonatedAccountTxSubmitterPropsSchema
>;

@ -2,6 +2,7 @@ import { z } from 'zod';
import { TxSubmitterType } from './TxSubmitterTypes.js';
import {
EV5GnosisSafeTxBuilderPropsSchema,
EV5GnosisSafeTxSubmitterPropsSchema,
EV5ImpersonatedAccountTxSubmitterPropsSchema,
} from './ethersV5/schemas.js';
@ -18,4 +19,8 @@ export const SubmitterMetadataSchema = z.discriminatedUnion('type', [
type: z.literal(TxSubmitterType.GNOSIS_SAFE),
...EV5GnosisSafeTxSubmitterPropsSchema.shape,
}),
z.object({
type: z.literal(TxSubmitterType.GNOSIS_TX_BUILDER),
...EV5GnosisSafeTxBuilderPropsSchema.shape,
}),
]);

@ -159,6 +159,7 @@ export const TOKEN_TYPE_TO_STANDARD: Record<TokenType, TokenStandard> = {
export const PROTOCOL_TO_NATIVE_STANDARD: Record<ProtocolType, TokenStandard> =
{
[ProtocolType.Ethereum]: TokenStandard.EvmNative,
[ProtocolType.GnosisTxBuilder]: TokenStandard.EvmNative,
[ProtocolType.Cosmos]: TokenStandard.CosmosNative,
[ProtocolType.Sealevel]: TokenStandard.SealevelNative,
};

@ -5,6 +5,7 @@ export enum ProtocolType {
Ethereum = 'ethereum',
Sealevel = 'sealevel',
Cosmos = 'cosmos',
GnosisTxBuilder = 'gnosisTxBuilder',
}
// A type that also allows for literal values of the enum
export type ProtocolTypeValue = `${ProtocolType}`;

Loading…
Cancel
Save