Repository: cosmos/relayer
Pull request #850 (2022-07-15 21:06:06)

Memo config and flag
Adds default memo to relayed transactions with `rly` and the version, e.g. `rly(v2.0.0)`.

Memo can be customized through the config file `memo` property, or the `--memo` flag. The flag will override the config property if provided.

Custom memos will have `rly` and the version appended, e.g. `memo` of `Strangelove Ventures` running on relayer version `v2.0.0` would result in a transaction memo of `Strangelove Ventures | rly(v2.0.0)`


Patch:
diff --git a/README.md b/README.md
index 5705688..f978c38 100644
--- a/README.md
+++ b/README.md
@@ -68,6 +68,19 @@ Additional information on how IBC works can be found [here](https://ibc.cosmos.n
    ```
    **Default config file location:** `~/.relayer/config/config.yaml`
 
+   By default, transactions will be relayed with a memo of `rly(VERSION)` e.g. `rly(v2.0.0)`.
+
+   To customize the memo for all relaying, use the `--memo` flag when initializing the configuration.
+
+   ```shell
+   $ rly config init --memo "My custom memo"
+   ```
+
+   Custom memos will have `rly(VERSION)` appended. For example, a memo of `My custom memo` running on relayer version `v2.0.0` would result in a transaction memo of `My custom memo | rly(v2.0.0)`. 
+   
+   The `--memo` flag is also available for other `rly` commands also that involve sending transactions such as `rly tx link` and `rly start`. It can be passed there to override the `config.yaml` value if desired.
+
+   To omit the memo entirely, including the default value of `rly(VERSION)`, use `-` for the memo.
 
 3. **Configure the chains you want to relay between.**
    
diff --git a/cmd/config.go b/cmd/config.go
index 13d0371..c6d1f17 100644
--- a/cmd/config.go
+++ b/cmd/config.go
@@ -46,7 +46,7 @@ func configCmd(a *appState) *cobra.Command {
 
 	cmd.AddCommand(
 		configShowCmd(a),
-		configInitCmd(),
+		configInitCmd(a),
 	)
 	return cmd
 }
@@ -108,7 +108,7 @@ $ %s cfg list`, appName, defaultHome, appName)),
 }
 
 // Command for initializing an empty config at the --home location
-func configInitCmd() *cobra.Command {
+func configInitCmd(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:     "init",
 		Aliases: []string{"i"},
@@ -150,8 +150,10 @@ $ %s cfg i`, appName, defaultHome, appName)),
 				}
 				defer f.Close()
 
+				memo, _ := cmd.Flags().GetString(flagMemo)
+
 				// And write the default config to that location...
-				if _, err = f.Write(defaultConfig()); err != nil {
+				if _, err = f.Write(defaultConfig(memo)); err != nil {
 					return err
 				}
 
@@ -163,7 +165,7 @@ $ %s cfg i`, appName, defaultHome, appName)),
 			return fmt.Errorf("config already exists: %s", cfgPath)
 		},
 	}
-	return cmd
+	return memoFlag(a.Viper, cmd)
 }
 
 // addChainsFromDirectory finds all JSON-encoded config files in dir,
@@ -272,6 +274,32 @@ func (c *Config) Wrapped() *ConfigOutputWrapper {
 	return &ConfigOutputWrapper{Global: c.Global, ProviderConfigs: providers, Paths: c.Paths}
 }
 
+// rlyMemo returns a formatted message memo string
+// that includes "rly" and the version, e.g. "rly(v2.0.0)"
+// or "My custom memo | rly(v2.0.0)"
+func rlyMemo(memo string) string {
+	if memo == "-" {
+		// omit memo entirely
+		return ""
+	}
+	defaultMemo := fmt.Sprintf("rly(%s)", Version)
+	if memo == "" {
+		return defaultMemo
+	}
+	return fmt.Sprintf("%s | %s", memo, defaultMemo)
+}
+
+// memo returns a formatted message memo string,
+// provided either by the memo flag or the config.
+func (c *Config) memo(cmd *cobra.Command) string {
+	memoFlag, _ := cmd.Flags().GetString(flagMemo)
+	if memoFlag != "" {
+		return rlyMemo(memoFlag)
+	}
+
+	return rlyMemo(c.Global.Memo)
+}
+
 // Config represents the config file for the relayer
 type Config struct {
 	Global GlobalConfig   `yaml:"global" json:"global"`
@@ -403,9 +431,9 @@ func (c Config) MustYAML() []byte {
 	return out
 }
 
-func defaultConfig() []byte {
+func defaultConfig(memo string) []byte {
 	return Config{
-		Global: newDefaultGlobalConfig(),
+		Global: newDefaultGlobalConfig(memo),
 		Chains: relayer.Chains{},
 		Paths:  relayer.Paths{},
 	}.MustYAML()
@@ -415,15 +443,17 @@ func defaultConfig() []byte {
 type GlobalConfig struct {
 	APIListenPort  string `yaml:"api-listen-addr" json:"api-listen-addr"`
 	Timeout        string `yaml:"timeout" json:"timeout"`
+	Memo           string `yaml:"memo" json:"memo"`
 	LightCacheSize int    `yaml:"light-cache-size" json:"light-cache-size"`
 }
 
 // newDefaultGlobalConfig returns a global config with defaults set
-func newDefaultGlobalConfig() GlobalConfig {
+func newDefaultGlobalConfig(memo string) GlobalConfig {
 	return GlobalConfig{
 		APIListenPort:  ":5183",
 		Timeout:        "10s",
 		LightCacheSize: 20,
+		Memo:           memo,
 	}
 }
 
diff --git a/cmd/flags.go b/cmd/flags.go
index 9551aab..cf0d826 100644
--- a/cmd/flags.go
+++ b/cmd/flags.go
@@ -44,6 +44,7 @@ const (
 	flagReverse                 = "reverse"
 	flagProcessor               = "processor"
 	flagInitialBlockHistory     = "block-history"
+	flagMemo                    = "memo"
 )
 
 const (
@@ -303,6 +304,14 @@ func processorFlags(v *viper.Viper, cmd *cobra.Command) *cobra.Command {
 	return cmd
 }
 
+func memoFlag(v *viper.Viper, cmd *cobra.Command) *cobra.Command {
+	cmd.Flags().String(flagMemo, "", "a memo to include in relayed packets")
+	if err := v.BindPFlag(flagMemo, cmd.Flags().Lookup(flagMemo)); err != nil {
+		panic(err)
+	}
+	return cmd
+}
+
 func OverwriteConfigFlag(v *viper.Viper, cmd *cobra.Command) *cobra.Command {
 	cmd.Flags().BoolP(flagOverwriteConfig, "o", false,
 		"overwrite already configured paths - will clear channel filter(s)")
diff --git a/cmd/start.go b/cmd/start.go
index c56a46a..1c0b7b1 100644
--- a/cmd/start.go
+++ b/cmd/start.go
@@ -91,7 +91,7 @@ $ %s start demo-path2 --max-tx-size 10`, appName, appName, appName)),
 				return err
 			}
 
-			rlyErrCh := relayer.StartRelayer(cmd.Context(), a.Log, c[src], c[dst], filter, maxTxSize, maxMsgLength, processorType, initialBlockHistory)
+			rlyErrCh := relayer.StartRelayer(cmd.Context(), a.Log, c[src], c[dst], filter, maxTxSize, maxMsgLength, a.Config.memo(cmd), processorType, initialBlockHistory)
 
 			// NOTE: This block of code is useful for ensuring that the clients tracking each chain do not expire
 			// when there are no packets flowing across the channels. It is currently a source of errors that have been
@@ -152,6 +152,7 @@ $ %s start demo-path2 --max-tx-size 10`, appName, appName, appName)),
 	cmd = strategyFlag(a.Viper, cmd)
 	cmd = debugServerFlags(a.Viper, cmd)
 	cmd = processorFlags(a.Viper, cmd)
+	cmd = memoFlag(a.Viper, cmd)
 	return cmd
 }
 
diff --git a/cmd/tx.go b/cmd/tx.go
index 07a6626..6a746fa 100644
--- a/cmd/tx.go
+++ b/cmd/tx.go
@@ -138,7 +138,7 @@ func createClientsCmd(a *appState) *cobra.Command {
 				return fmt.Errorf("key %s not found on dst chain %s", c[dst].ChainProvider.Key(), c[dst].ChainID())
 			}
 
-			modified, err := c[src].CreateClients(cmd.Context(), c[dst], allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, override)
+			modified, err := c[src].CreateClients(cmd.Context(), c[dst], allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, override, a.Config.memo(cmd))
 			if err != nil {
 				return err
 			}
@@ -152,7 +152,10 @@ func createClientsCmd(a *appState) *cobra.Command {
 		},
 	}
 
-	return overrideFlag(a.Viper, clientParameterFlags(a.Viper, cmd))
+	cmd = clientParameterFlags(a.Viper, cmd)
+	cmd = overrideFlag(a.Viper, cmd)
+	cmd = memoFlag(a.Viper, cmd)
+	return cmd
 }
 
 func createClientCmd(a *appState) *cobra.Command {
@@ -241,7 +244,7 @@ func createClientCmd(a *appState) *cobra.Command {
 				return err
 			}
 
-			modified, err := relayer.CreateClient(cmd.Context(), src, dst, srcUpdateHeader, dstUpdateHeader, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, override)
+			modified, err := relayer.CreateClient(cmd.Context(), src, dst, srcUpdateHeader, dstUpdateHeader, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, override, a.Config.memo(cmd))
 			if err != nil {
 				return err
 			}
@@ -255,7 +258,10 @@ func createClientCmd(a *appState) *cobra.Command {
 		},
 	}
 
-	return overrideFlag(a.Viper, clientParameterFlags(a.Viper, cmd))
+	cmd = clientParameterFlags(a.Viper, cmd)
+	cmd = overrideFlag(a.Viper, cmd)
+	cmd = memoFlag(a.Viper, cmd)
+	return cmd
 }
 
 func updateClientsCmd(a *appState) *cobra.Command {
@@ -281,11 +287,11 @@ corresponding update-client messages.`,
 				return fmt.Errorf("key %s not found on dst chain %s", c[dst].ChainProvider.Key(), c[dst].ChainID())
 			}
 
-			return c[src].UpdateClients(cmd.Context(), c[dst])
+			return c[src].UpdateClients(cmd.Context(), c[dst], a.Config.memo(cmd))
 		},
 	}
 
-	return cmd
+	return memoFlag(a.Viper, cmd)
 }
 
 func upgradeClientsCmd(a *appState) *cobra.Command {
@@ -314,16 +320,20 @@ func upgradeClientsCmd(a *appState) *cobra.Command {
 
 			targetChainID := args[1]
 
+			memo := a.Config.memo(cmd)
+
 			// send the upgrade message on the targetChainID
 			if src == targetChainID {
-				return c[src].UpgradeClients(cmd.Context(), c[dst], height)
+				return c[src].UpgradeClients(cmd.Context(), c[dst], height, memo)
 			}
 
-			return c[dst].UpgradeClients(cmd.Context(), c[src], height)
+			return c[dst].UpgradeClients(cmd.Context(), c[src], height, memo)
 		},
 	}
 
-	return heightFlag(a.Viper, cmd)
+	cmd = heightFlag(a.Viper, cmd)
+	cmd = memoFlag(a.Viper, cmd)
+	return cmd
 }
 
 func createConnectionCmd(a *appState) *cobra.Command {
@@ -379,8 +389,10 @@ $ %s tx conn demo-path --timeout 5s`,
 				return fmt.Errorf("key %s not found on dst chain %s", c[dst].ChainProvider.Key(), c[dst].ChainID())
 			}
 
+			memo := a.Config.memo(cmd)
+
 			// ensure that the clients exist
-			modified, err := c[src].CreateClients(cmd.Context(), c[dst], allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, override)
+			modified, err := c[src].CreateClients(cmd.Context(), c[dst], allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, override, memo)
 			if err != nil {
 				return err
 			}
@@ -390,7 +402,7 @@ $ %s tx conn demo-path --timeout 5s`,
 				}
 			}
 
-			modified, err = c[src].CreateOpenConnections(cmd.Context(), c[dst], retries, to)
+			modified, err = c[src].CreateOpenConnections(cmd.Context(), c[dst], retries, to, memo)
 			if err != nil {
 				return err
 			}
@@ -404,7 +416,12 @@ $ %s tx conn demo-path --timeout 5s`,
 		},
 	}
 
-	return overrideFlag(a.Viper, clientParameterFlags(a.Viper, retryFlag(a.Viper, timeoutFlag(a.Viper, cmd))))
+	cmd = timeoutFlag(a.Viper, cmd)
+	cmd = retryFlag(a.Viper, cmd)
+	cmd = clientParameterFlags(a.Viper, cmd)
+	cmd = overrideFlag(a.Viper, cmd)
+	cmd = memoFlag(a.Viper, cmd)
+	return cmd
 }
 
 func createChannelCmd(a *appState) *cobra.Command {
@@ -471,11 +488,16 @@ $ %s tx chan demo-path --timeout 5s --max-retries 10`,
 			}
 
 			// create channel if it isn't already created
-			return c[src].CreateOpenChannels(cmd.Context(), c[dst], retries, to, srcPort, dstPort, order, version, override)
+			return c[src].CreateOpenChannels(cmd.Context(), c[dst], retries, to, srcPort, dstPort, order, version, override, a.Config.memo(cmd))
 		},
 	}
 
-	return channelParameterFlags(a.Viper, overrideFlag(a.Viper, retryFlag(a.Viper, timeoutFlag(a.Viper, cmd))))
+	cmd = timeoutFlag(a.Viper, cmd)
+	cmd = retryFlag(a.Viper, cmd)
+	cmd = overrideFlag(a.Viper, cmd)
+	cmd = channelParameterFlags(a.Viper, cmd)
+	cmd = memoFlag(a.Viper, cmd)
+	return cmd
 }
 
 func closeChannelCmd(a *appState) *cobra.Command {
@@ -527,11 +549,14 @@ $ %s tx channel-close demo-path channel-0 transfer -o 3s`,
 				return err
 			}
 
-			return c[src].CloseChannel(cmd.Context(), c[dst], retries, to, channelID, portID)
+			return c[src].CloseChannel(cmd.Context(), c[dst], retries, to, channelID, portID, a.Config.memo(cmd))
 		},
 	}
 
-	return retryFlag(a.Viper, timeoutFlag(a.Viper, cmd))
+	cmd = timeoutFlag(a.Viper, cmd)
+	cmd = retryFlag(a.Viper, cmd)
+	cmd = memoFlag(a.Viper, cmd)
+	return cmd
 }
 
 func linkCmd(a *appState) *cobra.Command {
@@ -617,8 +642,10 @@ $ %s tx connect demo-path --src-port transfer --dst-port transfer --order unorde
 				return fmt.Errorf("key %s not found on dst chain %s", c[dst].ChainProvider.Key(), c[dst].ChainID())
 			}
 
+			memo := a.Config.memo(cmd)
+
 			// create clients if they aren't already created
-			modified, err := c[src].CreateClients(cmd.Context(), c[dst], allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, override)
+			modified, err := c[src].CreateClients(cmd.Context(), c[dst], allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, override, memo)
 			if err != nil {
 				return fmt.Errorf("error creating clients: %w", err)
 			}
@@ -629,7 +656,7 @@ $ %s tx connect demo-path --src-port transfer --dst-port transfer --order unorde
 			}
 
 			// create connection if it isn't already created
-			modified, err = c[src].CreateOpenConnections(cmd.Context(), c[dst], retries, to)
+			modified, err = c[src].CreateOpenConnections(cmd.Context(), c[dst], retries, to, memo)
 			if err != nil {
 				return fmt.Errorf("error creating connections: %w", err)
 			}
@@ -640,11 +667,16 @@ $ %s tx connect demo-path --src-port transfer --dst-port transfer --order unorde
 			}
 
 			// create channel if it isn't already created
-			return c[src].CreateOpenChannels(cmd.Context(), c[dst], retries, to, srcPort, dstPort, order, version, override)
+			return c[src].CreateOpenChannels(cmd.Context(), c[dst], retries, to, srcPort, dstPort, order, version, override, memo)
 		},
 	}
-
-	return overrideFlag(a.Viper, channelParameterFlags(a.Viper, clientParameterFlags(a.Viper, retryFlag(a.Viper, timeoutFlag(a.Viper, cmd)))))
+	cmd = timeoutFlag(a.Viper, cmd)
+	cmd = retryFlag(a.Viper, cmd)
+	cmd = clientParameterFlags(a.Viper, cmd)
+	cmd = channelParameterFlags(a.Viper, cmd)
+	cmd = overrideFlag(a.Viper, cmd)
+	cmd = memoFlag(a.Viper, cmd)
+	return cmd
 }
 
 func linkThenStartCmd(a *appState) *cobra.Command {
@@ -677,7 +709,14 @@ $ %s tx link-then-start demo-path --timeout 5s`, appName, appName)),
 		},
 	}
 
-	return overrideFlag(a.Viper, channelParameterFlags(a.Viper, clientParameterFlags(a.Viper, strategyFlag(a.Viper, retryFlag(a.Viper, timeoutFlag(a.Viper, cmd))))))
+	cmd = timeoutFlag(a.Viper, cmd)
+	cmd = retryFlag(a.Viper, cmd)
+	cmd = strategyFlag(a.Viper, cmd)
+	cmd = clientParameterFlags(a.Viper, cmd)
+	cmd = channelParameterFlags(a.Viper, cmd)
+	cmd = overrideFlag(a.Viper, cmd)
+	cmd = memoFlag(a.Viper, cmd)
+	return cmd
 }
 
 func relayMsgsCmd(a *appState) *cobra.Command {
@@ -714,7 +753,7 @@ $ %s tx relay-pkts demo-path channel-0`,
 
 			sp := relayer.UnrelayedSequences(cmd.Context(), c[src], c[dst], channel)
 
-			if err = relayer.RelayPackets(cmd.Context(), a.Log, c[src], c[dst], sp, maxTxSize, maxMsgLength, channel); err != nil {
+			if err = relayer.RelayPackets(cmd.Context(), a.Log, c[src], c[dst], sp, maxTxSize, maxMsgLength, a.Config.memo(cmd), channel); err != nil {
 				return err
 			}
 
@@ -722,7 +761,9 @@ $ %s tx relay-pkts demo-path channel-0`,
 		},
 	}
 
-	return strategyFlag(a.Viper, cmd)
+	cmd = strategyFlag(a.Viper, cmd)
+	cmd = memoFlag(a.Viper, cmd)
+	return cmd
 }
 
 func relayAcksCmd(a *appState) *cobra.Command {
@@ -761,7 +802,7 @@ $ %s tx relay-acks demo-path channel-0 -l 3 -s 6`,
 			// sp.Dst contains all sequences acked on DST but acknowledgement not processed on SRC
 			sp := relayer.UnrelayedAcknowledgements(cmd.Context(), c[src], c[dst], channel)
 
-			if err = relayer.RelayAcknowledgements(cmd.Context(), a.Log, c[src], c[dst], sp, maxTxSize, maxMsgLength, channel); err != nil {
+			if err = relayer.RelayAcknowledgements(cmd.Context(), a.Log, c[src], c[dst], sp, maxTxSize, maxMsgLength, a.Config.memo(cmd), channel); err != nil {
 				return err
 			}
 
@@ -769,7 +810,9 @@ $ %s tx relay-acks demo-path channel-0 -l 3 -s 6`,
 		},
 	}
 
-	return strategyFlag(a.Viper, cmd)
+	cmd = strategyFlag(a.Viper, cmd)
+	cmd = memoFlag(a.Viper, cmd)
+	return cmd
 }
 
 // TODO still needs a revisit
diff --git a/relayer/channel.go b/relayer/channel.go
index 05942e0..f3e598b 100644
--- a/relayer/channel.go
+++ b/relayer/channel.go
@@ -20,6 +20,7 @@ func (c *Chain) CreateOpenChannels(
 	timeout time.Duration,
 	srcPortID, dstPortID, order, version string,
 	override bool,
+	memo string,
 ) error {
 	// client and connection identifiers must be filled in
 	if err := ValidateConnectionPaths(c, dst); err != nil {
@@ -62,6 +63,7 @@ func (c *Chain) CreateOpenChannels(
 		c.log,
 		srcPathChain.pathEnd,
 		dstPathChain.pathEnd,
+		memo,
 	)
 
 	c.log.Info("Starting event processor for channel handshake",
@@ -104,7 +106,15 @@ func (c *Chain) CreateOpenChannels(
 }
 
 // CloseChannel runs the channel closing messages on timeout until they pass.
-func (c *Chain) CloseChannel(ctx context.Context, dst *Chain, maxRetries uint64, timeout time.Duration, srcChanID, srcPortID string) error {
+func (c *Chain) CloseChannel(
+	ctx context.Context,
+	dst *Chain,
+	maxRetries uint64,
+	timeout time.Duration,
+	srcChanID,
+	srcPortID string,
+	memo string,
+) error {
 	srcPathChain := pathChain{
 		provider: c.ChainProvider,
 		pathEnd:  processor.NewPathEnd(c.PathEnd.ChainID, c.PathEnd.ClientID, "", []processor.ChannelKey{}),
@@ -129,6 +139,7 @@ func (c *Chain) CloseChannel(ctx context.Context, dst *Chain, maxRetries uint64,
 			c.log,
 			srcPathChain.pathEnd,
 			dstPathChain.pathEnd,
+			memo,
 		)).
 		WithInitialBlockHistory(0).
 		WithMessageLifecycle(&processor.ChannelMessageLifecycle{
diff --git a/relayer/client.go b/relayer/client.go
index 8d9688d..ed88fe4 100644
--- a/relayer/client.go
+++ b/relayer/client.go
@@ -14,7 +14,7 @@ import (
 )
 
 // CreateClients creates clients for src on dst and dst on src if the client ids are unspecified.
-func (c *Chain) CreateClients(ctx context.Context, dst *Chain, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, override bool) (bool, error) {
+func (c *Chain) CreateClients(ctx context.Context, dst *Chain, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, override bool, memo string) (bool, error) {
 	// Query the latest heights on src and dst and retry if the query fails
 	var srch, dsth int64
 	if err := retry.Do(func() error {
@@ -58,7 +58,7 @@ func (c *Chain) CreateClients(ctx context.Context, dst *Chain, allowUpdateAfterE
 	eg.Go(func() error {
 		var err error
 		// Create client on src for dst if the client id is unspecified
-		modifiedSrc, err = CreateClient(egCtx, c, dst, srcUpdateHeader, dstUpdateHeader, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, override)
+		modifiedSrc, err = CreateClient(egCtx, c, dst, srcUpdateHeader, dstUpdateHeader, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, override, memo)
 		if err != nil {
 			return fmt.Errorf("failed to create client on src chain{%s}: %w", c.ChainID(), err)
 		}
@@ -68,7 +68,7 @@ func (c *Chain) CreateClients(ctx context.Context, dst *Chain, allowUpdateAfterE
 	eg.Go(func() error {
 		var err error
 		// Create client on dst for src if the client id is unspecified
-		modifiedDst, err = CreateClient(egCtx, dst, c, dstUpdateHeader, srcUpdateHeader, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, override)
+		modifiedDst, err = CreateClient(egCtx, dst, c, dstUpdateHeader, srcUpdateHeader, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, override, memo)
 		if err != nil {
 			return fmt.Errorf("failed to create client on dst chain{%s}: %w", dst.ChainID(), err)
 		}
@@ -91,7 +91,7 @@ func (c *Chain) CreateClients(ctx context.Context, dst *Chain, allowUpdateAfterE
 	return modifiedSrc || modifiedDst, nil
 }
 
-func CreateClient(ctx context.Context, src, dst *Chain, srcUpdateHeader, dstUpdateHeader ibcexported.Header, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, override bool) (bool, error) {
+func CreateClient(ctx context.Context, src, dst *Chain, srcUpdateHeader, dstUpdateHeader ibcexported.Header, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, override bool, memo string) (bool, error) {
 	// If a client ID was specified in the path, ensure it exists.
 	if src.PathEnd.ClientID != "" {
 		// TODO: check client is not expired
@@ -201,7 +201,7 @@ func CreateClient(ctx context.Context, src, dst *Chain, srcUpdateHeader, dstUpda
 	if err := retry.Do(func() error {
 		var success bool
 		var err error
-		res, success, err = src.ChainProvider.SendMessages(ctx, msgs)
+		res, success, err = src.ChainProvider.SendMessages(ctx, msgs, memo)
 		if err != nil {
 			src.LogFailedTx(res, err, msgs)
 			return fmt.Errorf("failed to send messages on chain{%s}: %w", src.ChainID(), err)
@@ -236,7 +236,7 @@ func CreateClient(ctx context.Context, src, dst *Chain, srcUpdateHeader, dstUpda
 }
 
 // UpdateClients updates clients for src on dst and dst on src given the configured paths
-func (c *Chain) UpdateClients(ctx context.Context, dst *Chain) (err error) {
+func (c *Chain) UpdateClients(ctx context.Context, dst *Chain, memo string) (err error) {
 	var (
 		srcUpdateHeader, dstUpdateHeader ibcexported.Header
 		srch, dsth                       int64
@@ -305,7 +305,7 @@ func (c *Chain) UpdateClients(ctx context.Context, dst *Chain) (err error) {
 	}
 
 	// Send msgs to both chains
-	result := clients.Send(ctx, c.log, AsRelayMsgSender(c), AsRelayMsgSender(dst))
+	result := clients.Send(ctx, c.log, AsRelayMsgSender(c), AsRelayMsgSender(dst), memo)
 	if err := result.Error(); err != nil {
 		if result.PartiallySent() {
 			c.log.Info(
@@ -335,7 +335,7 @@ func (c *Chain) UpdateClients(ctx context.Context, dst *Chain) (err error) {
 }
 
 // UpgradeClients upgrades the client on src after dst chain has undergone an upgrade.
-func (c *Chain) UpgradeClients(ctx context.Context, dst *Chain, height int64) error {
+func (c *Chain) UpgradeClients(ctx context.Context, dst *Chain, height int64, memo string) error {
 	dstHeader, err := dst.ChainProvider.GetLightSignedHeaderAtHeight(ctx, height)
 	if err != nil {
 		return err
@@ -375,7 +375,7 @@ func (c *Chain) UpgradeClients(ctx context.Context, dst *Chain, height int64) er
 		upgradeMsg,
 	}
 
-	res, _, err := c.ChainProvider.SendMessages(ctx, msgs)
+	res, _, err := c.ChainProvider.SendMessages(ctx, msgs, memo)
 	if err != nil {
 		c.LogFailedTx(res, err, msgs)
 		return err
diff --git a/relayer/connection.go b/relayer/connection.go
index 58f7e31..efc6222 100644
--- a/relayer/connection.go
+++ b/relayer/connection.go
@@ -16,6 +16,7 @@ func (c *Chain) CreateOpenConnections(
 	dst *Chain,
 	maxRetries uint64,
 	timeout time.Duration,
+	memo string,
 ) (modified bool, err error) {
 	// client identifiers must be filled in
 	if err = ValidateClientPaths(c, dst); err != nil {
@@ -41,6 +42,7 @@ func (c *Chain) CreateOpenConnections(
 		c.log,
 		srcpathChain.pathEnd,
 		dstpathChain.pathEnd,
+		memo,
 	)
 
 	pp.OnConnectionMessage(dst.PathEnd.ChainID, processor.MsgConnectionOpenConfirm, func(ci provider.ConnectionInfo) {
diff --git a/relayer/naive-strategy.go b/relayer/naive-strategy.go
index a90020a..cccec9f 100644
--- a/relayer/naive-strategy.go
+++ b/relayer/naive-strategy.go
@@ -383,7 +383,7 @@ func (rs *RelaySequences) Empty() bool {
 }
 
 // RelayAcknowledgements creates transactions to relay acknowledgements from src to dst and from dst to src
-func RelayAcknowledgements(ctx context.Context, log *zap.Logger, src, dst *Chain, sp RelaySequences, maxTxSize, maxMsgLength uint64, srcChannel *chantypes.IdentifiedChannel) error {
+func RelayAcknowledgements(ctx context.Context, log *zap.Logger, src, dst *Chain, sp RelaySequences, maxTxSize, maxMsgLength uint64, memo string, srcChannel *chantypes.IdentifiedChannel) error {
 	// set the maximum relay transaction constraints
 	msgs := &RelayMsgs{
 		Src:          []provider.RelayerMessage{},
@@ -481,7 +481,7 @@ func RelayAcknowledgements(ctx context.Context, log *zap.Logger, src, dst *Chain
 		}
 
 		// send messages to their respective chains
-		result := msgs.Send(ctx, log, AsRelayMsgSender(src), AsRelayMsgSender(dst))
+		result := msgs.Send(ctx, log, AsRelayMsgSender(src), AsRelayMsgSender(dst), memo)
 		if err := result.Error(); err != nil {
 			if result.PartiallySent() {
 				log.Info(
@@ -508,7 +508,7 @@ func RelayAcknowledgements(ctx context.Context, log *zap.Logger, src, dst *Chain
 }
 
 // RelayPackets creates transactions to relay packets from src to dst and from dst to src
-func RelayPackets(ctx context.Context, log *zap.Logger, src, dst *Chain, sp RelaySequences, maxTxSize, maxMsgLength uint64, srcChannel *chantypes.IdentifiedChannel) error {
+func RelayPackets(ctx context.Context, log *zap.Logger, src, dst *Chain, sp RelaySequences, maxTxSize, maxMsgLength uint64, memo string, srcChannel *chantypes.IdentifiedChannel) error {
 	// set the maximum relay transaction constraints
 	msgs := &RelayMsgs{
 		Src:          []provider.RelayerMessage{},
@@ -570,7 +570,7 @@ func RelayPackets(ctx context.Context, log *zap.Logger, src, dst *Chain, sp Rela
 		}
 
 		// send messages to their respective chains
-		result := msgs.Send(ctx, log, AsRelayMsgSender(src), AsRelayMsgSender(dst))
+		result := msgs.Send(ctx, log, AsRelayMsgSender(src), AsRelayMsgSender(dst), memo)
 		if err := result.Error(); err != nil {
 			if result.PartiallySent() {
 				log.Info(
diff --git a/relayer/packet-tx.go b/relayer/packet-tx.go
index 43ef210..589058d 100644
--- a/relayer/packet-tx.go
+++ b/relayer/packet-tx.go
@@ -88,7 +88,7 @@ func (c *Chain) SendTransferMsg(ctx context.Context, log *zap.Logger, dst *Chain
 		Src: []provider.RelayerMessage{msg},
 	}
 
-	result := txs.Send(ctx, log, AsRelayMsgSender(c), AsRelayMsgSender(dst))
+	result := txs.Send(ctx, log, AsRelayMsgSender(c), AsRelayMsgSender(dst), "")
 	if err := result.Error(); err != nil {
 		if result.PartiallySent() {
 			c.log.Info(
diff --git a/relayer/path.go b/relayer/path.go
index cedd1b2..0b68632 100644
--- a/relayer/path.go
+++ b/relayer/path.go
@@ -88,6 +88,7 @@ type PathAction struct {
 }
 
 // Path represents a pair of chains and the identifiers needed to relay over them along with a channel filter list.
+// A Memo can optionally be provided for identification in relayed messages.
 type Path struct {
 	Src    *PathEnd      `yaml:"src" json:"src"`
 	Dst    *PathEnd      `yaml:"dst" json:"dst"`
diff --git a/relayer/processor/path_processor.go b/relayer/processor/path_processor.go
index 9c961c9..01adf42 100644
--- a/relayer/processor/path_processor.go
+++ b/relayer/processor/path_processor.go
@@ -53,6 +53,8 @@ type PathProcessor struct {
 	pathEnd1 *pathEndRuntime
 	pathEnd2 *pathEndRuntime
 
+	memo string
+
 	// Signals to retry.
 	retryProcess chan struct{}
 
@@ -71,12 +73,13 @@ func (p PathProcessors) IsRelayedChannel(k ChannelKey, chainID string) bool {
 	return false
 }
 
-func NewPathProcessor(log *zap.Logger, pathEnd1 PathEnd, pathEnd2 PathEnd) *PathProcessor {
+func NewPathProcessor(log *zap.Logger, pathEnd1 PathEnd, pathEnd2 PathEnd, memo string) *PathProcessor {
 	return &PathProcessor{
 		log:          log,
 		pathEnd1:     newPathEndRuntime(log, pathEnd1),
 		pathEnd2:     newPathEndRuntime(log, pathEnd2),
 		retryProcess: make(chan struct{}, 2),
+		memo:         memo,
 	}
 }
 
diff --git a/relayer/processor/path_processor_internal.go b/relayer/processor/path_processor_internal.go
index e04573f..b1212f2 100644
--- a/relayer/processor/path_processor_internal.go
+++ b/relayer/processor/path_processor_internal.go
@@ -688,7 +688,7 @@ func (pp *PathProcessor) assembleAndSendMessages(
 	ctx, cancel := context.WithTimeout(ctx, messageSendTimeout)
 	defer cancel()
 
-	_, txSuccess, err := dst.chainProvider.SendMessages(ctx, om.msgs)
+	_, txSuccess, err := dst.chainProvider.SendMessages(ctx, om.msgs, pp.memo)
 	if err != nil {
 		return fmt.Errorf("error sending messages: %w", err)
 	}
diff --git a/relayer/provider/cosmos/tx.go b/relayer/provider/cosmos/tx.go
index 032740d..97f3a97 100644
--- a/relayer/provider/cosmos/tx.go
+++ b/relayer/provider/cosmos/tx.go
@@ -59,8 +59,8 @@ var (
 
 // SendMessage attempts to sign, encode & send a RelayerMessage
 // This is used extensively in the relayer as an extension of the Provider interface
-func (cc *CosmosProvider) SendMessage(ctx context.Context, msg provider.RelayerMessage) (*provider.RelayerTxResponse, bool, error) {
-	return cc.SendMessages(ctx, []provider.RelayerMessage{msg})
+func (cc *CosmosProvider) SendMessage(ctx context.Context, msg provider.RelayerMessage, memo string) (*provider.RelayerTxResponse, bool, error) {
+	return cc.SendMessages(ctx, []provider.RelayerMessage{msg}, memo)
 }
 
 // SendMessages attempts to sign, encode, & send a slice of RelayerMessages
@@ -70,11 +70,11 @@ func (cc *CosmosProvider) SendMessage(ctx context.Context, msg provider.RelayerM
 // transaction will not return an error. If a transaction is successfully sent, the result of the execution
 // of that transaction will be logged. A boolean indicating if a transaction was successfully
 // sent and executed successfully is returned.
-func (cc *CosmosProvider) SendMessages(ctx context.Context, msgs []provider.RelayerMessage) (*provider.RelayerTxResponse, bool, error) {
+func (cc *CosmosProvider) SendMessages(ctx context.Context, msgs []provider.RelayerMessage, memo string) (*provider.RelayerTxResponse, bool, error) {
 	var resp *sdk.TxResponse
 
 	if err := retry.Do(func() error {
-		txBytes, err := cc.buildMessages(ctx, msgs)
+		txBytes, err := cc.buildMessages(ctx, msgs, memo)
 		if err != nil {
 			errMsg := err.Error()
 
@@ -209,13 +209,17 @@ func parseEventsFromTxResponse(resp *sdk.TxResponse) []provider.RelayerEvent {
 	return events
 }
 
-func (cc *CosmosProvider) buildMessages(ctx context.Context, msgs []provider.RelayerMessage) ([]byte, error) {
+func (cc *CosmosProvider) buildMessages(ctx context.Context, msgs []provider.RelayerMessage, memo string) ([]byte, error) {
 	// Query account details
 	txf, err := cc.PrepareFactory(cc.TxFactory())
 	if err != nil {
 		return nil, err
 	}
 
+	if memo != "" {
+		txf = txf.WithMemo(memo)
+	}
+
 	// TODO: Make this work with new CalculateGas method
 	// TODO: This is related to GRPC client stuff?
 	// https://github.com/cosmos/cosmos-sdk/blob/5725659684fc93790a63981c653feee33ecf3225/client/tx/tx.go#L297
@@ -804,7 +808,7 @@ func (cc *CosmosProvider) AutoUpdateClient(ctx context.Context, dst provider.Cha
 
 	msgs := []provider.RelayerMessage{updateMsg}
 
-	res, success, err := cc.SendMessages(ctx, msgs)
+	res, success, err := cc.SendMessages(ctx, msgs, "")
 	if err != nil {
 		// cp.LogFailedTx(res, err, CosmosMsgs(msgs...))
 		return 0, err
@@ -1125,7 +1129,10 @@ func (cc *CosmosProvider) PacketCommitment(
 	}, nil
 }
 
-func (cc *CosmosProvider) MsgRecvPacket(msgTransfer provider.PacketInfo, proof provider.PacketProof) (provider.RelayerMessage, error) {
+func (cc *CosmosProvider) MsgRecvPacket(
+	msgTransfer provider.PacketInfo,
+	proof provider.PacketProof,
+) (provider.RelayerMessage, error) {
 	signer, err := cc.Address()
 	if err != nil {
 		return nil, err
@@ -1159,7 +1166,10 @@ func (cc *CosmosProvider) PacketAcknowledgement(
 	}, nil
 }
 
-func (cc *CosmosProvider) MsgAcknowledgement(msgRecvPacket provider.PacketInfo, proof provider.PacketProof) (provider.RelayerMessage, error) {
+func (cc *CosmosProvider) MsgAcknowledgement(
+	msgRecvPacket provider.PacketInfo,
+	proof provider.PacketProof,
+) (provider.RelayerMessage, error) {
 	signer, err := cc.Address()
 	if err != nil {
 		return nil, err
diff --git a/relayer/provider/provider.go b/relayer/provider/provider.go
index 4b95165..f69d2c6 100644
--- a/relayer/provider/provider.go
+++ b/relayer/provider/provider.go
@@ -214,13 +214,15 @@ type ChainProvider interface {
 	// and assembles a full MsgAcknowledgement ready to write to the chain.
 	MsgAcknowledgement(msgRecvPacket PacketInfo, proofAcked PacketProof) (RelayerMessage, error)
 
-	// MsgTimeout takes the packet information from a MsgTransfer along with the packet receipt to prove that the packet was never relayed,
+	// MsgTimeout takes the packet information from a MsgTransfer along
+	// with the packet receipt to prove that the packet was never relayed,
 	// i.e. that the MsgRecvPacket was never written to the counterparty chain,
 	// and assembles a full MsgTimeout ready to write to the chain,
 	// i.e. the chain where the MsgTransfer was committed.
 	MsgTimeout(msgTransfer PacketInfo, proofUnreceived PacketProof) (RelayerMessage, error)
 
-	// MsgTimeoutOnClose takes the packet information from a MsgTransfer along with the packet receipt to prove that the packet was never relayed,
+	// MsgTimeoutOnClose takes the packet information from a MsgTransfer along
+	// with the packet receipt to prove that the packet was never relayed,
 	// i.e. that the MsgRecvPacket was never written to the counterparty chain,
 	// and assembles a full MsgTimeoutOnClose ready to write to the chain,
 	// i.e. the chain where the MsgTransfer was committed.
@@ -310,8 +312,8 @@ type ChainProvider interface {
 	RelayPacketFromSequence(ctx context.Context, src, dst ChainProvider, srch, dsth, seq uint64, dstChanId, dstPortId, dstClientId, srcChanId, srcPortId, srcClientId string, order chantypes.Order) (RelayerMessage, RelayerMessage, error)
 	AcknowledgementFromSequence(ctx context.Context, dst ChainProvider, dsth, seq uint64, dstChanId, dstPortId, srcChanId, srcPortId string) (RelayerMessage, error)
 
-	SendMessage(ctx context.Context, msg RelayerMessage) (*RelayerTxResponse, bool, error)
-	SendMessages(ctx context.Context, msgs []RelayerMessage) (*RelayerTxResponse, bool, error)
+	SendMessage(ctx context.Context, msg RelayerMessage, memo string) (*RelayerTxResponse, bool, error)
+	SendMessages(ctx context.Context, msgs []RelayerMessage, memo string) (*RelayerTxResponse, bool, error)
 
 	// TODO consolidate with IBCHeaderAtHeight
 	GetLightSignedHeaderAtHeight(ctx context.Context, h int64) (ibcexported.Header, error)
diff --git a/relayer/relayMsgs.go b/relayer/relayMsgs.go
index c5d1364..fc8181c 100644
--- a/relayer/relayMsgs.go
+++ b/relayer/relayMsgs.go
@@ -51,7 +51,7 @@ type RelayMsgSender struct {
 	// on the ChainProvider interface.
 	//
 	// Accepting this narrow subset of the interface greatly simplifies testing.
-	SendMessages func(context.Context, []provider.RelayerMessage) (*provider.RelayerTxResponse, bool, error)
+	SendMessages func(context.Context, []provider.RelayerMessage, string) (*provider.RelayerTxResponse, bool, error)
 }
 
 // AsRelayMsgSender converts c to a RelayMsgSender.
@@ -110,7 +110,7 @@ func (r SendMsgsResult) MarshalLogObject(enc zapcore.ObjectEncoder) error {
 }
 
 // Send concurrently sends out r's messages to the corresponding RelayMsgSenders.
-func (r *RelayMsgs) Send(ctx context.Context, log *zap.Logger, src, dst RelayMsgSender) SendMsgsResult {
+func (r *RelayMsgs) Send(ctx context.Context, log *zap.Logger, src, dst RelayMsgSender, memo string) SendMsgsResult {
 	var (
 		wg     sync.WaitGroup
 		result SendMsgsResult
@@ -118,11 +118,11 @@ func (r *RelayMsgs) Send(ctx context.Context, log *zap.Logger, src, dst RelayMsg
 
 	if len(r.Src) > 0 {
 		wg.Add(1)
-		go r.send(ctx, log, &wg, src, r.Src, &result.SuccessfulSrcBatches, &result.SrcSendError)
+		go r.send(ctx, log, &wg, src, r.Src, memo, &result.SuccessfulSrcBatches, &result.SrcSendError)
 	}
 	if len(r.Dst) > 0 {
 		wg.Add(1)
-		go r.send(ctx, log, &wg, dst, r.Dst, &result.SuccessfulDstBatches, &result.DstSendError)
+		go r.send(ctx, log, &wg, dst, r.Dst, memo, &result.SuccessfulDstBatches, &result.DstSendError)
 	}
 
 	wg.Wait()
@@ -135,6 +135,7 @@ func (r *RelayMsgs) send(
 	wg *sync.WaitGroup,
 	s RelayMsgSender,
 	msgs []provider.RelayerMessage,
+	memo string,
 	successes *int,
 	errors *error,
 ) {
@@ -165,7 +166,7 @@ func (r *RelayMsgs) send(
 		// Send out this batch now.
 		batchMsgs := msgs[batchStartIdx:i]
 		batchCtx, batchCtxCancel := context.WithTimeout(ctx, batchSendMessageTimeout)
-		resp, success, err := s.SendMessages(batchCtx, batchMsgs)
+		resp, success, err := s.SendMessages(batchCtx, batchMsgs, memo)
 		batchCtxCancel()
 		if err != nil {
 			logFailedTx(log, s.ChainID, resp, err, batchMsgs)
@@ -187,7 +188,7 @@ func (r *RelayMsgs) send(
 	if batchStartIdx < uint64(len(msgs)) {
 		batchMsgs := msgs[batchStartIdx:]
 		batchCtx, batchCtxCancel := context.WithTimeout(ctx, batchSendMessageTimeout)
-		resp, success, err := s.SendMessages(batchCtx, batchMsgs)
+		resp, success, err := s.SendMessages(batchCtx, batchMsgs, memo)
 		batchCtxCancel()
 		if err != nil {
 			logFailedTx(log, s.ChainID, resp, err, batchMsgs)
diff --git a/relayer/strategies.go b/relayer/strategies.go
index c15535b..eb7ca25 100644
--- a/relayer/strategies.go
+++ b/relayer/strategies.go
@@ -35,6 +35,7 @@ func StartRelayer(
 	src, dst *Chain,
 	filter ChannelFilter,
 	maxTxSize, maxMsgLength uint64,
+	memo string,
 	processorType string,
 	initialBlockHistory uint64,
 ) chan error {
@@ -61,10 +62,10 @@ func StartRelayer(
 			},
 		}}
 
-		go relayerStartEventProcessor(ctx, log, paths, initialBlockHistory, maxTxSize, maxMsgLength, errorChan)
+		go relayerStartEventProcessor(ctx, log, paths, initialBlockHistory, maxTxSize, maxMsgLength, memo, errorChan)
 		return errorChan
 	case ProcessorLegacy:
-		go relayerMainLoop(ctx, log, src, dst, filter, maxTxSize, maxMsgLength, errorChan)
+		go relayerMainLoop(ctx, log, src, dst, filter, maxTxSize, maxMsgLength, memo, errorChan)
 		return errorChan
 	default:
 		panic(fmt.Errorf("unexpected processor type: %s, supports one of: [%s, %s]", processorType, ProcessorEvents, ProcessorLegacy))
@@ -95,7 +96,16 @@ func (chain pathChain) chainProcessor(log *zap.Logger) processor.ChainProcessor
 }
 
 // relayerStartEventProcessor is the main relayer process when using the event processor.
-func relayerStartEventProcessor(ctx context.Context, log *zap.Logger, paths []path, initialBlockHistory uint64, maxTxSize, maxMsgLength uint64, errCh chan<- error) {
+func relayerStartEventProcessor(
+	ctx context.Context,
+	log *zap.Logger,
+	paths []path,
+	initialBlockHistory uint64,
+	maxTxSize,
+	maxMsgLength uint64,
+	memo string,
+	errCh chan<- error,
+) {
 	defer close(errCh)
 
 	epb := processor.NewEventProcessor()
@@ -110,6 +120,7 @@ func relayerStartEventProcessor(ctx context.Context, log *zap.Logger, paths []pa
 				log,
 				p.src.pathEnd,
 				p.dst.pathEnd,
+				memo,
 			))
 	}
 
@@ -121,7 +132,7 @@ func relayerStartEventProcessor(ctx context.Context, log *zap.Logger, paths []pa
 }
 
 // relayerMainLoop is the main loop of the relayer.
-func relayerMainLoop(ctx context.Context, log *zap.Logger, src, dst *Chain, filter ChannelFilter, maxTxSize, maxMsgLength uint64, errCh chan<- error) {
+func relayerMainLoop(ctx context.Context, log *zap.Logger, src, dst *Chain, filter ChannelFilter, maxTxSize, maxMsgLength uint64, memo string, errCh chan<- error) {
 	defer close(errCh)
 
 	// Query the list of channels on the src connection.
@@ -165,7 +176,7 @@ func relayerMainLoop(ctx context.Context, log *zap.Logger, src, dst *Chain, filt
 			if !channel.active {
 				channel.active = true
 				wg.Add(1)
-				go relayUnrelayedPacketsAndAcks(ctx, log, &wg, src, dst, maxTxSize, maxMsgLength, channel, channels)
+				go relayUnrelayedPacketsAndAcks(ctx, log, &wg, src, dst, maxTxSize, maxMsgLength, memo, channel, channels)
 			}
 		}
 
@@ -292,7 +303,7 @@ func applyChannelFilterRule(filter ChannelFilter, channels []*types.IdentifiedCh
 }
 
 // relayUnrelayedPacketsAndAcks will relay all the pending packets and acknowledgements on both the src and dst chains.
-func relayUnrelayedPacketsAndAcks(ctx context.Context, log *zap.Logger, wg *sync.WaitGroup, src, dst *Chain, maxTxSize, maxMsgLength uint64, srcChannel *ActiveChannel, channels chan<- *ActiveChannel) {
+func relayUnrelayedPacketsAndAcks(ctx context.Context, log *zap.Logger, wg *sync.WaitGroup, src, dst *Chain, maxTxSize, maxMsgLength uint64, memo string, srcChannel *ActiveChannel, channels chan<- *ActiveChannel) {
 	// make goroutine signal its death, whether it's a panic or a return
 	defer func() {
 		wg.Done()
@@ -300,10 +311,10 @@ func relayUnrelayedPacketsAndAcks(ctx context.Context, log *zap.Logger, wg *sync
 	}()
 
 	for {
-		if ok := relayUnrelayedPackets(ctx, log, src, dst, maxTxSize, maxMsgLength, srcChannel.channel); !ok {
+		if ok := relayUnrelayedPackets(ctx, log, src, dst, maxTxSize, maxMsgLength, memo, srcChannel.channel); !ok {
 			return
 		}
-		if ok := relayUnrelayedAcks(ctx, log, src, dst, maxTxSize, maxMsgLength, srcChannel.channel); !ok {
+		if ok := relayUnrelayedAcks(ctx, log, src, dst, maxTxSize, maxMsgLength, memo, srcChannel.channel); !ok {
 			return
 		}
 
@@ -320,7 +331,7 @@ func relayUnrelayedPacketsAndAcks(ctx context.Context, log *zap.Logger, wg *sync
 // relayUnrelayedPackets fetches unrelayed packet sequence numbers and attempts to relay the associated packets.
 // relayUnrelayedPackets returns true if packets were empty or were successfully relayed.
 // Otherwise, it logs the errors and returns false.
-func relayUnrelayedPackets(ctx context.Context, log *zap.Logger, src, dst *Chain, maxTxSize, maxMsgLength uint64, srcChannel *types.IdentifiedChannel) bool {
+func relayUnrelayedPackets(ctx context.Context, log *zap.Logger, src, dst *Chain, maxTxSize, maxMsgLength uint64, memo string, srcChannel *types.IdentifiedChannel) bool {
 	// Fetch any unrelayed sequences depending on the channel order
 	sp := UnrelayedSequences(ctx, src, dst, srcChannel)
 
@@ -356,7 +367,7 @@ func relayUnrelayedPackets(ctx context.Context, log *zap.Logger, src, dst *Chain
 		)
 	}
 
-	if err := RelayPackets(ctx, log, src, dst, sp, maxTxSize, maxMsgLength, srcChannel); err != nil {
+	if err := RelayPackets(ctx, log, src, dst, sp, maxTxSize, maxMsgLength, memo, srcChannel); err != nil {
 		// If there was a context cancellation or deadline while attempting to relay packets,
 		// log that and indicate failure.
 		if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
@@ -403,7 +414,7 @@ func relayUnrelayedPackets(ctx context.Context, log *zap.Logger, src, dst *Chain
 // relayUnrelayedAcks fetches unrelayed acknowledgements and attempts to relay them.
 // relayUnrelayedAcks returns true if acknowledgements were empty or were successfully relayed.
 // Otherwise, it logs the errors and returns false.
-func relayUnrelayedAcks(ctx context.Context, log *zap.Logger, src, dst *Chain, maxTxSize, maxMsgLength uint64, srcChannel *types.IdentifiedChannel) bool {
+func relayUnrelayedAcks(ctx context.Context, log *zap.Logger, src, dst *Chain, maxTxSize, maxMsgLength uint64, memo string, srcChannel *types.IdentifiedChannel) bool {
 	// Fetch any unrelayed acks depending on the channel order
 	ap := UnrelayedAcknowledgements(ctx, src, dst, srcChannel)
 
@@ -439,7 +450,7 @@ func relayUnrelayedAcks(ctx context.Context, log *zap.Logger, src, dst *Chain, m
 		)
 	}
 
-	if err := RelayAcknowledgements(ctx, log, src, dst, ap, maxTxSize, maxMsgLength, srcChannel); err != nil {
+	if err := RelayAcknowledgements(ctx, log, src, dst, ap, maxTxSize, maxMsgLength, memo, srcChannel); err != nil {
 		// If there was a context cancellation or deadline while attempting to relay acknowledgements,
 		// log that and indicate failure.
 		if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {


Test patch:
diff --git a/_test/relayer_chain_test.go b/_test/relayer_chain_test.go
index f8d1e8c..cdf17ab 100644
--- a/_test/relayer_chain_test.go
+++ b/_test/relayer_chain_test.go
@@ -75,7 +75,7 @@ func chainTest(t *testing.T, tcs []testChain) {
 	require.NoError(t, eg.Wait())
 
 	t.Log("Creating clients")
-	_, err = src.CreateClients(ctx, dst, true, true, false)
+	_, err = src.CreateClients(ctx, dst, true, true, false, "")
 	require.NoError(t, err)
 	testClientPair(ctx, t, src, dst)
 
@@ -83,12 +83,12 @@ func chainTest(t *testing.T, tcs []testChain) {
 	require.NoError(t, err)
 
 	t.Log("Creating connections")
-	_, err = src.CreateOpenConnections(ctx, dst, 3, timeout)
+	_, err = src.CreateOpenConnections(ctx, dst, 3, timeout, "")
 	require.NoError(t, err)
 	testConnectionPair(ctx, t, src, dst)
 
 	t.Log("Creating channels")
-	err = src.CreateOpenChannels(ctx, dst, 3, timeout, DefaultSrcPortID, DefaultDstPortID, DefaultOrder, DefaultVersion, false)
+	err = src.CreateOpenChannels(ctx, dst, 3, timeout, DefaultSrcPortID, DefaultDstPortID, DefaultOrder, DefaultVersion, false, "")
 	require.NoError(t, err)
 
 	t.Log("Querying open channels to ensure successful creation")
@@ -119,7 +119,7 @@ func chainTest(t *testing.T, tcs []testChain) {
 
 	t.Log("Starting relayer")
 	filter := relayer.ChannelFilter{}
-	_ = relayer.StartRelayer(ctx, log, src, dst, filter, 2*cmd.MB, 5, relayer.ProcessorEvents, 20)
+	_ = relayer.StartRelayer(ctx, log, src, dst, filter, 2*cmd.MB, 5, "", relayer.ProcessorEvents, 20)
 
 	t.Log("Waiting for relayer messages to reach both chains")
 	require.NoError(t, src.ChainProvider.WaitForNBlocks(ctx, 2))
@@ -164,18 +164,18 @@ func TestGaiaReuseIdentifiers(t *testing.T) {
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 
-	_, err = src.CreateClients(ctx, dst, true, true, false)
+	_, err = src.CreateClients(ctx, dst, true, true, false, "")
 	require.NoError(t, err)
 	testClientPair(ctx, t, src, dst)
 
 	timeout, err := src.GetTimeout()
 	require.NoError(t, err)
 
-	_, err = src.CreateOpenConnections(ctx, dst, 3, timeout)
+	_, err = src.CreateOpenConnections(ctx, dst, 3, timeout, "")
 	require.NoError(t, err)
 	testConnectionPair(ctx, t, src, dst)
 
-	err = src.CreateOpenChannels(ctx, dst, 3, timeout, DefaultSrcPortID, DefaultDstPortID, DefaultOrder, DefaultVersion, false)
+	err = src.CreateOpenChannels(ctx, dst, 3, timeout, DefaultSrcPortID, DefaultDstPortID, DefaultOrder, DefaultVersion, false, "")
 	require.NoError(t, err)
 
 	// query open channels and ensure there is no error
@@ -194,15 +194,15 @@ func TestGaiaReuseIdentifiers(t *testing.T) {
 	dst.PathEnd.ClientID = ""
 	dst.PathEnd.ConnectionID = ""
 
-	_, err = src.CreateClients(ctx, dst, true, true, false)
+	_, err = src.CreateClients(ctx, dst, true, true, false, "")
 	require.NoError(t, err)
 	testClientPair(ctx, t, src, dst)
 
-	_, err = src.CreateOpenConnections(ctx, dst, 3, timeout)
+	_, err = src.CreateOpenConnections(ctx, dst, 3, timeout, "")
 	require.NoError(t, err)
 	testConnectionPair(ctx, t, src, dst)
 
-	err = src.CreateOpenChannels(ctx, dst, 3, timeout, DefaultSrcPortID, DefaultDstPortID, DefaultOrder, DefaultVersion, false)
+	err = src.CreateOpenChannels(ctx, dst, 3, timeout, DefaultSrcPortID, DefaultDstPortID, DefaultOrder, DefaultVersion, false, "")
 	require.NoError(t, err)
 	testChannelPair(ctx, t, src, dst, channel.ChannelId, channel.PortId)
 
@@ -216,7 +216,7 @@ func TestGaiaReuseIdentifiers(t *testing.T) {
 	src.PathEnd.ClientID = ""
 	dst.PathEnd.ClientID = ""
 
-	_, err = src.CreateClients(ctx, dst, true, true, true)
+	_, err = src.CreateClients(ctx, dst, true, true, true, "")
 	require.NoError(t, err)
 	testClientPair(ctx, t, src, dst)
 
@@ -245,18 +245,18 @@ func TestGaiaMisbehaviourMonitoring(t *testing.T) {
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 
-	_, err = src.CreateClients(ctx, dst, true, true, false)
+	_, err = src.CreateClients(ctx, dst, true, true, false, "")
 	require.NoError(t, err)
 	testClientPair(ctx, t, src, dst)
 
 	timeout, err := src.GetTimeout()
 	require.NoError(t, err)
 
-	_, err = src.CreateOpenConnections(ctx, dst, 3, timeout)
+	_, err = src.CreateOpenConnections(ctx, dst, 3, timeout, "")
 	require.NoError(t, err)
 	testConnectionPair(ctx, t, src, dst)
 
-	err = src.CreateOpenChannels(ctx, dst, 3, timeout, DefaultSrcPortID, DefaultDstPortID, DefaultOrder, DefaultVersion, false)
+	err = src.CreateOpenChannels(ctx, dst, 3, timeout, DefaultSrcPortID, DefaultDstPortID, DefaultOrder, DefaultVersion, false, "")
 	require.NoError(t, err)
 
 	// query open channels and ensure there is no error
@@ -269,7 +269,7 @@ func TestGaiaMisbehaviourMonitoring(t *testing.T) {
 	log := zaptest.NewLogger(t)
 	// start the relayer process in it's own goroutine
 	filter := relayer.ChannelFilter{}
-	_ = relayer.StartRelayer(ctx, log, src, dst, filter, 2*cmd.MB, 5, relayer.ProcessorEvents, 20)
+	_ = relayer.StartRelayer(ctx, log, src, dst, filter, 2*cmd.MB, 5, "", relayer.ProcessorEvents, 20)
 
 	// Wait for relay message inclusion in both chains
 	require.NoError(t, src.ChainProvider.WaitForNBlocks(ctx, 1))
@@ -312,7 +312,7 @@ func TestGaiaMisbehaviourMonitoring(t *testing.T) {
 	updateMsg, err := src.ChainProvider.MsgUpdateClient(src.PathEnd.ClientID, newHeader)
 	require.NoError(t, err)
 
-	res, success, err := src.ChainProvider.SendMessage(ctx, updateMsg)
+	res, success, err := src.ChainProvider.SendMessage(ctx, updateMsg, "")
 	require.NoError(t, err)
 	require.True(t, success)
 	require.Equal(t, uint32(0), res.Code)
@@ -385,7 +385,7 @@ func TestRelayAllChannelsOnConnection(t *testing.T) {
 	require.NoError(t, eg.Wait())
 
 	t.Log("Creating clients")
-	_, err = src.CreateClients(ctx, dst, true, true, false)
+	_, err = src.CreateClients(ctx, dst, true, true, false, "")
 	require.NoError(t, err)
 	testClientPair(ctx, t, src, dst)
 
@@ -393,15 +393,15 @@ func TestRelayAllChannelsOnConnection(t *testing.T) {
 	require.NoError(t, err)
 
 	t.Log("Creating connections")
-	_, err = src.CreateOpenConnections(ctx, dst, 3, timeout)
+	_, err = src.CreateOpenConnections(ctx, dst, 3, timeout, "")
 	require.NoError(t, err)
 	testConnectionPair(ctx, t, src, dst)
 
 	t.Log("Creating channels")
-	err = src.CreateOpenChannels(ctx, dst, 3, timeout, DefaultSrcPortID, DefaultDstPortID, DefaultOrder, DefaultVersion, false)
+	err = src.CreateOpenChannels(ctx, dst, 3, timeout, DefaultSrcPortID, DefaultDstPortID, DefaultOrder, DefaultVersion, false, "")
 	require.NoError(t, err)
 
-	err = src.CreateOpenChannels(ctx, dst, 3, timeout, DefaultSrcPortID, DefaultDstPortID, DefaultOrder, DefaultVersion, true)
+	err = src.CreateOpenChannels(ctx, dst, 3, timeout, DefaultSrcPortID, DefaultDstPortID, DefaultOrder, DefaultVersion, true, "")
 	require.NoError(t, err)
 
 	t.Log("Ensuring two channels exist")
@@ -443,7 +443,7 @@ func TestRelayAllChannelsOnConnection(t *testing.T) {
 
 	t.Log("Starting relayer")
 	filter := relayer.ChannelFilter{}
-	_ = relayer.StartRelayer(ctx, log, src, dst, filter, 2*cmd.MB, 5, relayer.ProcessorEvents, 20)
+	_ = relayer.StartRelayer(ctx, log, src, dst, filter, 2*cmd.MB, 5, "", relayer.ProcessorEvents, 20)
 
 	t.Log("Waiting for relayer message inclusion in both chains")
 	require.NoError(t, src.ChainProvider.WaitForNBlocks(ctx, 1))
@@ -582,18 +582,18 @@ func TestUnorderedChannelBlockHeightTimeout(t *testing.T) {
 	require.NoError(t, eg.Wait())
 
 	// create path
-	_, err = src.CreateClients(ctx, dst, true, true, false)
+	_, err = src.CreateClients(ctx, dst, true, true, false, "")
 	require.NoError(t, err)
 	testClientPair(ctx, t, src, dst)
 
 	timeout, err := src.GetTimeout()
 	require.NoError(t, err)
 
-	_, err = src.CreateOpenConnections(ctx, dst, 3, timeout)
+	_, err = src.CreateOpenConnections(ctx, dst, 3, timeout, "")
 	require.NoError(t, err)
 	testConnectionPair(ctx, t, src, dst)
 
-	err = src.CreateOpenChannels(ctx, dst, 3, timeout, DefaultSrcPortID, DefaultDstPortID, DefaultOrder, DefaultVersion, false)
+	err = src.CreateOpenChannels(ctx, dst, 3, timeout, DefaultSrcPortID, DefaultDstPortID, DefaultOrder, DefaultVersion, false, "")
 	require.NoError(t, err)
 
 	// query open channels and ensure there is no error
@@ -619,7 +619,7 @@ func TestUnorderedChannelBlockHeightTimeout(t *testing.T) {
 
 	// start the relayer process in it's own goroutine
 	filter := relayer.ChannelFilter{}
-	_ = relayer.StartRelayer(ctx, log, src, dst, filter, 2*cmd.MB, 5, relayer.ProcessorEvents, 20)
+	_ = relayer.StartRelayer(ctx, log, src, dst, filter, 2*cmd.MB, 5, "", relayer.ProcessorEvents, 20)
 
 	require.NoError(t, src.ChainProvider.WaitForNBlocks(ctx, 5))
 
@@ -680,18 +680,18 @@ func TestUnorderedChannelTimestampTimeout(t *testing.T) {
 	require.NoError(t, eg.Wait())
 
 	// create path
-	_, err = src.CreateClients(ctx, dst, true, true, false)
+	_, err = src.CreateClients(ctx, dst, true, true, false, "")
 	require.NoError(t, err)
 	testClientPair(ctx, t, src, dst)
 
 	timeout, err := src.GetTimeout()
 	require.NoError(t, err)
 
-	_, err = src.CreateOpenConnections(ctx, dst, 3, timeout)
+	_, err = src.CreateOpenConnections(ctx, dst, 3, timeout, "")
 	require.NoError(t, err)
 	testConnectionPair(ctx, t, src, dst)
 
-	err = src.CreateOpenChannels(ctx, dst, 3, timeout, DefaultSrcPortID, DefaultDstPortID, DefaultOrder, DefaultVersion, false)
+	err = src.CreateOpenChannels(ctx, dst, 3, timeout, DefaultSrcPortID, DefaultDstPortID, DefaultOrder, DefaultVersion, false, "")
 	require.NoError(t, err)
 
 	// query open channels and ensure there is no error
@@ -717,7 +717,7 @@ func TestUnorderedChannelTimestampTimeout(t *testing.T) {
 
 	// start the relayer process in it's own goroutine
 	filter := relayer.ChannelFilter{}
-	_ = relayer.StartRelayer(ctx, log, src, dst, filter, 2*cmd.MB, 5, relayer.ProcessorEvents, 20)
+	_ = relayer.StartRelayer(ctx, log, src, dst, filter, 2*cmd.MB, 5, "", relayer.ProcessorEvents, 20)
 
 	time.Sleep(time.Second * 10)
 
diff --git a/relayer/chains/mock/mock_chain_processor_test.go b/relayer/chains/mock/mock_chain_processor_test.go
index 446dc7a..983307e 100644
--- a/relayer/chains/mock/mock_chain_processor_test.go
+++ b/relayer/chains/mock/mock_chain_processor_test.go
@@ -54,7 +54,7 @@ func TestMockChainAndPathProcessors(t *testing.T) {
 	ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*20)
 	defer ctxCancel()
 
-	pathProcessor := processor.NewPathProcessor(log, pathEnd1, pathEnd2)
+	pathProcessor := processor.NewPathProcessor(log, pathEnd1, pathEnd2, "")
 
 	eventProcessor := processor.NewEventProcessor().
 		WithChainProcessors(
diff --git a/relayer/relaymsgs_test.go b/relayer/relaymsgs_test.go
index 1739c8a..dab0990 100644
--- a/relayer/relaymsgs_test.go
+++ b/relayer/relaymsgs_test.go
@@ -62,7 +62,7 @@ func TestRelayMsgs_Send_Success(t *testing.T) {
 	var srcSent []provider.RelayerMessage
 	src := relayer.RelayMsgSender{
 		ChainID: "src",
-		SendMessages: func(ctx context.Context, msgs []provider.RelayerMessage) (*provider.RelayerTxResponse, bool, error) {
+		SendMessages: func(ctx context.Context, msgs []provider.RelayerMessage, memo string) (*provider.RelayerTxResponse, bool, error) {
 			srcSent = append(srcSent, msgs...)
 			return nil, true, nil
 		},
@@ -71,7 +71,7 @@ func TestRelayMsgs_Send_Success(t *testing.T) {
 	var dstSent []provider.RelayerMessage
 	dst := relayer.RelayMsgSender{
 		ChainID: "dst",
-		SendMessages: func(ctx context.Context, msgs []provider.RelayerMessage) (*provider.RelayerTxResponse, bool, error) {
+		SendMessages: func(ctx context.Context, msgs []provider.RelayerMessage, memo string) (*provider.RelayerTxResponse, bool, error) {
 			dstSent = append(dstSent, msgs...)
 			return nil, true, nil
 		},
@@ -90,7 +90,7 @@ func TestRelayMsgs_Send_Success(t *testing.T) {
 			Dst: []provider.RelayerMessage{dstMsg},
 		}
 
-		result := rm.Send(context.Background(), zaptest.NewLogger(t), src, dst)
+		result := rm.Send(context.Background(), zaptest.NewLogger(t), src, dst, "")
 		require.Equal(t, result.SuccessfulSrcBatches, 1)
 		require.Equal(t, result.SuccessfulDstBatches, 1)
 		require.NoError(t, result.SrcSendError)
@@ -112,7 +112,7 @@ func TestRelayMsgs_Send_Success(t *testing.T) {
 			MaxMsgLength: 2,
 		}
 
-		result := rm.Send(context.Background(), zaptest.NewLogger(t), src, dst)
+		result := rm.Send(context.Background(), zaptest.NewLogger(t), src, dst, "")
 		require.Equal(t, result.SuccessfulSrcBatches, 2)
 		require.Equal(t, result.SuccessfulDstBatches, 2)
 		require.NoError(t, result.SrcSendError)
@@ -134,7 +134,7 @@ func TestRelayMsgs_Send_Success(t *testing.T) {
 			MaxMsgLength: 2,
 		}
 
-		result := rm.Send(context.Background(), zaptest.NewLogger(t), src, dst)
+		result := rm.Send(context.Background(), zaptest.NewLogger(t), src, dst, "")
 		require.Equal(t, result.SuccessfulSrcBatches, 2)
 		require.Equal(t, result.SuccessfulDstBatches, 2)
 		require.NoError(t, result.SrcSendError)
@@ -150,7 +150,7 @@ func TestRelayMsgs_Send_Errors(t *testing.T) {
 		srcErr := fmt.Errorf("source error")
 		src := relayer.RelayMsgSender{
 			ChainID: "src",
-			SendMessages: func(ctx context.Context, msgs []provider.RelayerMessage) (*provider.RelayerTxResponse, bool, error) {
+			SendMessages: func(ctx context.Context, msgs []provider.RelayerMessage, memo string) (*provider.RelayerTxResponse, bool, error) {
 				return nil, false, srcErr
 			},
 		}
@@ -158,7 +158,7 @@ func TestRelayMsgs_Send_Errors(t *testing.T) {
 		dstErr := fmt.Errorf("dest error")
 		dst := relayer.RelayMsgSender{
 			ChainID: "dst",
-			SendMessages: func(ctx context.Context, msgs []provider.RelayerMessage) (*provider.RelayerTxResponse, bool, error) {
+			SendMessages: func(ctx context.Context, msgs []provider.RelayerMessage, memo string) (*provider.RelayerTxResponse, bool, error) {
 				return nil, false, dstErr
 			},
 		}
@@ -171,7 +171,7 @@ func TestRelayMsgs_Send_Errors(t *testing.T) {
 			Dst: []provider.RelayerMessage{dstMsg},
 		}
 
-		result := rm.Send(context.Background(), zaptest.NewLogger(t), src, dst)
+		result := rm.Send(context.Background(), zaptest.NewLogger(t), src, dst, "")
 		require.Equal(t, result.SuccessfulSrcBatches, 0)
 		require.Equal(t, result.SuccessfulDstBatches, 0)
 		require.ErrorIs(t, result.SrcSendError, srcErr)
@@ -183,7 +183,7 @@ func TestRelayMsgs_Send_Errors(t *testing.T) {
 		var srcCalls int
 		src := relayer.RelayMsgSender{
 			ChainID: "src",
-			SendMessages: func(ctx context.Context, msgs []provider.RelayerMessage) (*provider.RelayerTxResponse, bool, error) {
+			SendMessages: func(ctx context.Context, msgs []provider.RelayerMessage, memo string) (*provider.RelayerTxResponse, bool, error) {
 				srcCalls++
 				switch srcCalls {
 				case 1:
@@ -200,7 +200,7 @@ func TestRelayMsgs_Send_Errors(t *testing.T) {
 		var dstCalls int
 		dst := relayer.RelayMsgSender{
 			ChainID: "dst",
-			SendMessages: func(ctx context.Context, msgs []provider.RelayerMessage) (*provider.RelayerTxResponse, bool, error) {
+			SendMessages: func(ctx context.Context, msgs []provider.RelayerMessage, memo string) (*provider.RelayerTxResponse, bool, error) {
 				dstCalls++
 				switch dstCalls {
 				case 1:
@@ -223,7 +223,7 @@ func TestRelayMsgs_Send_Errors(t *testing.T) {
 			MaxMsgLength: 2,
 		}
 
-		result := rm.Send(context.Background(), zaptest.NewLogger(t), src, dst)
+		result := rm.Send(context.Background(), zaptest.NewLogger(t), src, dst, "")
 		require.Equal(t, result.SuccessfulSrcBatches, 0)
 		require.Equal(t, result.SuccessfulDstBatches, 0)
 		require.ErrorIs(t, result.SrcSendError, srcErr1)
@@ -237,7 +237,7 @@ func TestRelayMsgs_Send_Errors(t *testing.T) {
 		var srcCalls int
 		src := relayer.RelayMsgSender{
 			ChainID: "src",
-			SendMessages: func(ctx context.Context, msgs []provider.RelayerMessage) (*provider.RelayerTxResponse, bool, error) {
+			SendMessages: func(ctx context.Context, msgs []provider.RelayerMessage, memo string) (*provider.RelayerTxResponse, bool, error) {
 				srcCalls++
 				switch srcCalls {
 				case 1:
@@ -254,7 +254,7 @@ func TestRelayMsgs_Send_Errors(t *testing.T) {
 		var dstCalls int
 		dst := relayer.RelayMsgSender{
 			ChainID: "dst",
-			SendMessages: func(ctx context.Context, msgs []provider.RelayerMessage) (*provider.RelayerTxResponse, bool, error) {
+			SendMessages: func(ctx context.Context, msgs []provider.RelayerMessage, memo string) (*provider.RelayerTxResponse, bool, error) {
 				dstCalls++
 				switch dstCalls {
 				case 1:
@@ -277,7 +277,7 @@ func TestRelayMsgs_Send_Errors(t *testing.T) {
 			MaxMsgLength: 2,
 		}
 
-		result := rm.Send(context.Background(), zaptest.NewLogger(t), src, dst)
+		result := rm.Send(context.Background(), zaptest.NewLogger(t), src, dst, "")
 		require.Equal(t, result.SuccessfulSrcBatches, 1)
 		require.Equal(t, result.SuccessfulDstBatches, 1)
 		require.ErrorIs(t, result.SrcSendError, srcErr)