From 093e77704b5092e01c86b91ac5e898179b83a4b8 Mon Sep 17 00:00:00 2001 From: Sreeraj S Date: Thu, 5 Feb 2026 01:02:35 +0530 Subject: [PATCH] feat(abstract-cosmos,sdk-coin-hash): add group proposal support Ticket: WIN-8842 --- .../resources/ProposalCompiled.d.ts | 262 +++++++ .../resources/ProposalCompiled.js | 696 ++++++++++++++++++ .../src/lib/ContractCallBuilder.ts | 26 +- modules/abstract-cosmos/src/lib/constants.ts | 1 + modules/abstract-cosmos/src/lib/iface.ts | 2 +- .../abstract-cosmos/src/lib/protobuf-init.ts | 25 + .../abstract-cosmos/src/lib/transaction.ts | 21 + .../src/lib/transactionBuilder.ts | 16 +- modules/abstract-cosmos/src/lib/utils.ts | 71 +- modules/sdk-coin-hash/test/resources/hash.ts | 21 + .../transactionBuilder/contractCallBuilder.ts | 99 +++ 11 files changed, 1218 insertions(+), 22 deletions(-) create mode 100644 modules/abstract-cosmos/resources/ProposalCompiled.d.ts create mode 100644 modules/abstract-cosmos/resources/ProposalCompiled.js create mode 100644 modules/abstract-cosmos/src/lib/protobuf-init.ts create mode 100644 modules/sdk-coin-hash/test/unit/transactionBuilder/contractCallBuilder.ts diff --git a/modules/abstract-cosmos/resources/ProposalCompiled.d.ts b/modules/abstract-cosmos/resources/ProposalCompiled.d.ts new file mode 100644 index 0000000000..b718ff1703 --- /dev/null +++ b/modules/abstract-cosmos/resources/ProposalCompiled.d.ts @@ -0,0 +1,262 @@ +import * as $protobuf from 'protobufjs'; +/** Namespace cosmos. */ +export namespace cosmos { + /** Namespace group. */ + namespace group { + /** Namespace v1. */ + namespace v1 { + /** Properties of a MsgSubmitProposal. */ + interface IMsgSubmitProposal { + /** MsgSubmitProposal groupPolicyAddress */ + groupPolicyAddress?: string | null; + + /** MsgSubmitProposal proposers */ + proposers?: string[] | null; + + /** MsgSubmitProposal metadata */ + metadata?: string | null; + + /** MsgSubmitProposal messages */ + messages?: google.protobuf.IAny[] | null; + + /** MsgSubmitProposal exec */ + exec?: cosmos.group.v1.Exec | null; + + /** MsgSubmitProposal title */ + title?: string | null; + + /** MsgSubmitProposal summary */ + summary?: string | null; + } + + /** Represents a MsgSubmitProposal. */ + class MsgSubmitProposal implements IMsgSubmitProposal { + /** + * Constructs a new MsgSubmitProposal. + * @param [properties] Properties to set + */ + constructor(properties?: cosmos.group.v1.IMsgSubmitProposal); + + /** MsgSubmitProposal groupPolicyAddress. */ + public groupPolicyAddress: string; + + /** MsgSubmitProposal proposers. */ + public proposers: string[]; + + /** MsgSubmitProposal metadata. */ + public metadata: string; + + /** MsgSubmitProposal messages. */ + public messages: google.protobuf.IAny[]; + + /** MsgSubmitProposal exec. */ + public exec: cosmos.group.v1.Exec; + + /** MsgSubmitProposal title. */ + public title: string; + + /** MsgSubmitProposal summary. */ + public summary: string; + + /** + * Creates a new MsgSubmitProposal instance using the specified properties. + * @param [properties] Properties to set + * @returns MsgSubmitProposal instance + */ + public static create(properties?: cosmos.group.v1.IMsgSubmitProposal): cosmos.group.v1.MsgSubmitProposal; + + /** + * Encodes the specified MsgSubmitProposal message. Does not implicitly {@link cosmos.group.v1.MsgSubmitProposal.verify|verify} messages. + * @param message MsgSubmitProposal message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: cosmos.group.v1.IMsgSubmitProposal, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified MsgSubmitProposal message, length delimited. Does not implicitly {@link cosmos.group.v1.MsgSubmitProposal.verify|verify} messages. + * @param message MsgSubmitProposal message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited( + message: cosmos.group.v1.IMsgSubmitProposal, + writer?: $protobuf.Writer + ): $protobuf.Writer; + + /** + * Decodes a MsgSubmitProposal message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns MsgSubmitProposal + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: $protobuf.Reader | Uint8Array, length?: number): cosmos.group.v1.MsgSubmitProposal; + + /** + * Decodes a MsgSubmitProposal message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns MsgSubmitProposal + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: $protobuf.Reader | Uint8Array): cosmos.group.v1.MsgSubmitProposal; + + /** + * Verifies a MsgSubmitProposal message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): string | null; + + /** + * Creates a MsgSubmitProposal message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns MsgSubmitProposal + */ + public static fromObject(object: { [k: string]: any }): cosmos.group.v1.MsgSubmitProposal; + + /** + * Creates a plain object from a MsgSubmitProposal message. Also converts values to other types if specified. + * @param message MsgSubmitProposal + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject( + message: cosmos.group.v1.MsgSubmitProposal, + options?: $protobuf.IConversionOptions + ): { [k: string]: any }; + + /** + * Converts this MsgSubmitProposal to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for MsgSubmitProposal + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + /** Exec enum. */ + enum Exec { + EXEC_UNSPECIFIED = 0, + EXEC_TRY = 1, + } + } + } +} + +/** Namespace google. */ +export namespace google { + /** Namespace protobuf. */ + namespace protobuf { + /** Properties of an Any. */ + interface IAny { + /** Any type_url */ + type_url?: string | null; + + /** Any value */ + value?: Uint8Array | null; + } + + /** Represents an Any. */ + class Any implements IAny { + /** + * Constructs a new Any. + * @param [properties] Properties to set + */ + constructor(properties?: google.protobuf.IAny); + + /** Any type_url. */ + public type_url: string; + + /** Any value. */ + public value: Uint8Array; + + /** + * Creates a new Any instance using the specified properties. + * @param [properties] Properties to set + * @returns Any instance + */ + public static create(properties?: google.protobuf.IAny): google.protobuf.Any; + + /** + * Encodes the specified Any message. Does not implicitly {@link google.protobuf.Any.verify|verify} messages. + * @param message Any message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: google.protobuf.IAny, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified Any message, length delimited. Does not implicitly {@link google.protobuf.Any.verify|verify} messages. + * @param message Any message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: google.protobuf.IAny, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes an Any message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns Any + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: $protobuf.Reader | Uint8Array, length?: number): google.protobuf.Any; + + /** + * Decodes an Any message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns Any + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: $protobuf.Reader | Uint8Array): google.protobuf.Any; + + /** + * Verifies an Any message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): string | null; + + /** + * Creates an Any message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns Any + */ + public static fromObject(object: { [k: string]: any }): google.protobuf.Any; + + /** + * Creates a plain object from an Any message. Also converts values to other types if specified. + * @param message Any + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject( + message: google.protobuf.Any, + options?: $protobuf.IConversionOptions + ): { [k: string]: any }; + + /** + * Converts this Any to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for Any + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + } +} diff --git a/modules/abstract-cosmos/resources/ProposalCompiled.js b/modules/abstract-cosmos/resources/ProposalCompiled.js new file mode 100644 index 0000000000..f8302f8c1d --- /dev/null +++ b/modules/abstract-cosmos/resources/ProposalCompiled.js @@ -0,0 +1,696 @@ +/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars*/ +'use strict'; + +var $protobuf = require('protobufjs/minimal'); + +// Common aliases +var $Reader = $protobuf.Reader, + $Writer = $protobuf.Writer, + $util = $protobuf.util; + +// Exported root namespace +var $root = $protobuf.roots['default'] || ($protobuf.roots['default'] = {}); + +$root.cosmos = (function () { + /** + * Namespace cosmos. + * @exports cosmos + * @namespace + */ + var cosmos = {}; + + cosmos.group = (function () { + /** + * Namespace group. + * @memberof cosmos + * @namespace + */ + var group = {}; + + group.v1 = (function () { + /** + * Namespace v1. + * @memberof cosmos.group + * @namespace + */ + var v1 = {}; + + v1.MsgSubmitProposal = (function () { + /** + * Properties of a MsgSubmitProposal. + * @memberof cosmos.group.v1 + * @interface IMsgSubmitProposal + * @property {string|null} [groupPolicyAddress] MsgSubmitProposal groupPolicyAddress + * @property {Array.|null} [proposers] MsgSubmitProposal proposers + * @property {string|null} [metadata] MsgSubmitProposal metadata + * @property {Array.|null} [messages] MsgSubmitProposal messages + * @property {cosmos.group.v1.Exec|null} [exec] MsgSubmitProposal exec + * @property {string|null} [title] MsgSubmitProposal title + * @property {string|null} [summary] MsgSubmitProposal summary + */ + + /** + * Constructs a new MsgSubmitProposal. + * @memberof cosmos.group.v1 + * @classdesc Represents a MsgSubmitProposal. + * @implements IMsgSubmitProposal + * @constructor + * @param {cosmos.group.v1.IMsgSubmitProposal=} [properties] Properties to set + */ + function MsgSubmitProposal(properties) { + this.proposers = []; + this.messages = []; + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) this[keys[i]] = properties[keys[i]]; + } + + /** + * MsgSubmitProposal groupPolicyAddress. + * @member {string} groupPolicyAddress + * @memberof cosmos.group.v1.MsgSubmitProposal + * @instance + */ + MsgSubmitProposal.prototype.groupPolicyAddress = ''; + + /** + * MsgSubmitProposal proposers. + * @member {Array.} proposers + * @memberof cosmos.group.v1.MsgSubmitProposal + * @instance + */ + MsgSubmitProposal.prototype.proposers = $util.emptyArray; + + /** + * MsgSubmitProposal metadata. + * @member {string} metadata + * @memberof cosmos.group.v1.MsgSubmitProposal + * @instance + */ + MsgSubmitProposal.prototype.metadata = ''; + + /** + * MsgSubmitProposal messages. + * @member {Array.} messages + * @memberof cosmos.group.v1.MsgSubmitProposal + * @instance + */ + MsgSubmitProposal.prototype.messages = $util.emptyArray; + + /** + * MsgSubmitProposal exec. + * @member {cosmos.group.v1.Exec} exec + * @memberof cosmos.group.v1.MsgSubmitProposal + * @instance + */ + MsgSubmitProposal.prototype.exec = 0; + + /** + * MsgSubmitProposal title. + * @member {string} title + * @memberof cosmos.group.v1.MsgSubmitProposal + * @instance + */ + MsgSubmitProposal.prototype.title = ''; + + /** + * MsgSubmitProposal summary. + * @member {string} summary + * @memberof cosmos.group.v1.MsgSubmitProposal + * @instance + */ + MsgSubmitProposal.prototype.summary = ''; + + /** + * Creates a new MsgSubmitProposal instance using the specified properties. + * @function create + * @memberof cosmos.group.v1.MsgSubmitProposal + * @static + * @param {cosmos.group.v1.IMsgSubmitProposal=} [properties] Properties to set + * @returns {cosmos.group.v1.MsgSubmitProposal} MsgSubmitProposal instance + */ + MsgSubmitProposal.create = function create(properties) { + return new MsgSubmitProposal(properties); + }; + + /** + * Encodes the specified MsgSubmitProposal message. Does not implicitly {@link cosmos.group.v1.MsgSubmitProposal.verify|verify} messages. + * @function encode + * @memberof cosmos.group.v1.MsgSubmitProposal + * @static + * @param {cosmos.group.v1.IMsgSubmitProposal} message MsgSubmitProposal message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + MsgSubmitProposal.encode = function encode(message, writer) { + if (!writer) writer = $Writer.create(); + if (message.groupPolicyAddress != null && Object.hasOwnProperty.call(message, 'groupPolicyAddress')) + writer.uint32(/* id 1, wireType 2 =*/ 10).string(message.groupPolicyAddress); + if (message.proposers != null && message.proposers.length) + for (var i = 0; i < message.proposers.length; ++i) + writer.uint32(/* id 2, wireType 2 =*/ 18).string(message.proposers[i]); + if (message.metadata != null && Object.hasOwnProperty.call(message, 'metadata')) + writer.uint32(/* id 3, wireType 2 =*/ 26).string(message.metadata); + if (message.messages != null && message.messages.length) + for (var i = 0; i < message.messages.length; ++i) + $root.google.protobuf.Any.encode( + message.messages[i], + writer.uint32(/* id 4, wireType 2 =*/ 34).fork() + ).ldelim(); + if (message.exec != null && Object.hasOwnProperty.call(message, 'exec')) + writer.uint32(/* id 5, wireType 0 =*/ 40).int32(message.exec); + if (message.title != null && Object.hasOwnProperty.call(message, 'title')) + writer.uint32(/* id 6, wireType 2 =*/ 50).string(message.title); + if (message.summary != null && Object.hasOwnProperty.call(message, 'summary')) + writer.uint32(/* id 7, wireType 2 =*/ 58).string(message.summary); + return writer; + }; + + /** + * Encodes the specified MsgSubmitProposal message, length delimited. Does not implicitly {@link cosmos.group.v1.MsgSubmitProposal.verify|verify} messages. + * @function encodeDelimited + * @memberof cosmos.group.v1.MsgSubmitProposal + * @static + * @param {cosmos.group.v1.IMsgSubmitProposal} message MsgSubmitProposal message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + MsgSubmitProposal.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a MsgSubmitProposal message from the specified reader or buffer. + * @function decode + * @memberof cosmos.group.v1.MsgSubmitProposal + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {cosmos.group.v1.MsgSubmitProposal} MsgSubmitProposal + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + MsgSubmitProposal.decode = function decode(reader, length, error) { + if (!(reader instanceof $Reader)) reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.cosmos.group.v1.MsgSubmitProposal(); + while (reader.pos < end) { + var tag = reader.uint32(); + if (tag === error) break; + switch (tag >>> 3) { + case 1: { + message.groupPolicyAddress = reader.string(); + break; + } + case 2: { + if (!(message.proposers && message.proposers.length)) message.proposers = []; + message.proposers.push(reader.string()); + break; + } + case 3: { + message.metadata = reader.string(); + break; + } + case 4: { + if (!(message.messages && message.messages.length)) message.messages = []; + message.messages.push($root.google.protobuf.Any.decode(reader, reader.uint32())); + break; + } + case 5: { + message.exec = reader.int32(); + break; + } + case 6: { + message.title = reader.string(); + break; + } + case 7: { + message.summary = reader.string(); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a MsgSubmitProposal message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof cosmos.group.v1.MsgSubmitProposal + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {cosmos.group.v1.MsgSubmitProposal} MsgSubmitProposal + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + MsgSubmitProposal.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a MsgSubmitProposal message. + * @function verify + * @memberof cosmos.group.v1.MsgSubmitProposal + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + MsgSubmitProposal.verify = function verify(message) { + if (typeof message !== 'object' || message === null) return 'object expected'; + if (message.groupPolicyAddress != null && message.hasOwnProperty('groupPolicyAddress')) + if (!$util.isString(message.groupPolicyAddress)) return 'groupPolicyAddress: string expected'; + if (message.proposers != null && message.hasOwnProperty('proposers')) { + if (!Array.isArray(message.proposers)) return 'proposers: array expected'; + for (var i = 0; i < message.proposers.length; ++i) + if (!$util.isString(message.proposers[i])) return 'proposers: string[] expected'; + } + if (message.metadata != null && message.hasOwnProperty('metadata')) + if (!$util.isString(message.metadata)) return 'metadata: string expected'; + if (message.messages != null && message.hasOwnProperty('messages')) { + if (!Array.isArray(message.messages)) return 'messages: array expected'; + for (var i = 0; i < message.messages.length; ++i) { + var error = $root.google.protobuf.Any.verify(message.messages[i]); + if (error) return 'messages.' + error; + } + } + if (message.exec != null && message.hasOwnProperty('exec')) + switch (message.exec) { + default: + return 'exec: enum value expected'; + case 0: + case 1: + break; + } + if (message.title != null && message.hasOwnProperty('title')) + if (!$util.isString(message.title)) return 'title: string expected'; + if (message.summary != null && message.hasOwnProperty('summary')) + if (!$util.isString(message.summary)) return 'summary: string expected'; + return null; + }; + + /** + * Creates a MsgSubmitProposal message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof cosmos.group.v1.MsgSubmitProposal + * @static + * @param {Object.} object Plain object + * @returns {cosmos.group.v1.MsgSubmitProposal} MsgSubmitProposal + */ + MsgSubmitProposal.fromObject = function fromObject(object) { + if (object instanceof $root.cosmos.group.v1.MsgSubmitProposal) return object; + var message = new $root.cosmos.group.v1.MsgSubmitProposal(); + if (object.groupPolicyAddress != null) message.groupPolicyAddress = String(object.groupPolicyAddress); + if (object.proposers) { + if (!Array.isArray(object.proposers)) + throw TypeError('.cosmos.group.v1.MsgSubmitProposal.proposers: array expected'); + message.proposers = []; + for (var i = 0; i < object.proposers.length; ++i) message.proposers[i] = String(object.proposers[i]); + } + if (object.metadata != null) message.metadata = String(object.metadata); + if (object.messages) { + if (!Array.isArray(object.messages)) + throw TypeError('.cosmos.group.v1.MsgSubmitProposal.messages: array expected'); + message.messages = []; + for (var i = 0; i < object.messages.length; ++i) { + if (typeof object.messages[i] !== 'object') + throw TypeError('.cosmos.group.v1.MsgSubmitProposal.messages: object expected'); + message.messages[i] = $root.google.protobuf.Any.fromObject(object.messages[i]); + } + } + switch (object.exec) { + default: + if (typeof object.exec === 'number') { + message.exec = object.exec; + break; + } + break; + case 'EXEC_UNSPECIFIED': + case 0: + message.exec = 0; + break; + case 'EXEC_TRY': + case 1: + message.exec = 1; + break; + } + if (object.title != null) message.title = String(object.title); + if (object.summary != null) message.summary = String(object.summary); + return message; + }; + + /** + * Creates a plain object from a MsgSubmitProposal message. Also converts values to other types if specified. + * @function toObject + * @memberof cosmos.group.v1.MsgSubmitProposal + * @static + * @param {cosmos.group.v1.MsgSubmitProposal} message MsgSubmitProposal + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + MsgSubmitProposal.toObject = function toObject(message, options) { + if (!options) options = {}; + var object = {}; + if (options.arrays || options.defaults) { + object.proposers = []; + object.messages = []; + } + if (options.defaults) { + object.groupPolicyAddress = ''; + object.metadata = ''; + object.exec = options.enums === String ? 'EXEC_UNSPECIFIED' : 0; + object.title = ''; + object.summary = ''; + } + if (message.groupPolicyAddress != null && message.hasOwnProperty('groupPolicyAddress')) + object.groupPolicyAddress = message.groupPolicyAddress; + if (message.proposers && message.proposers.length) { + object.proposers = []; + for (var j = 0; j < message.proposers.length; ++j) object.proposers[j] = message.proposers[j]; + } + if (message.metadata != null && message.hasOwnProperty('metadata')) object.metadata = message.metadata; + if (message.messages && message.messages.length) { + object.messages = []; + for (var j = 0; j < message.messages.length; ++j) + object.messages[j] = $root.google.protobuf.Any.toObject(message.messages[j], options); + } + if (message.exec != null && message.hasOwnProperty('exec')) + object.exec = + options.enums === String + ? $root.cosmos.group.v1.Exec[message.exec] === undefined + ? message.exec + : $root.cosmos.group.v1.Exec[message.exec] + : message.exec; + if (message.title != null && message.hasOwnProperty('title')) object.title = message.title; + if (message.summary != null && message.hasOwnProperty('summary')) object.summary = message.summary; + return object; + }; + + /** + * Converts this MsgSubmitProposal to JSON. + * @function toJSON + * @memberof cosmos.group.v1.MsgSubmitProposal + * @instance + * @returns {Object.} JSON object + */ + MsgSubmitProposal.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for MsgSubmitProposal + * @function getTypeUrl + * @memberof cosmos.group.v1.MsgSubmitProposal + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + MsgSubmitProposal.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = 'type.googleapis.com'; + } + return typeUrlPrefix + '/cosmos.group.v1.MsgSubmitProposal'; + }; + + return MsgSubmitProposal; + })(); + + /** + * Exec enum. + * @name cosmos.group.v1.Exec + * @enum {number} + * @property {number} EXEC_UNSPECIFIED=0 EXEC_UNSPECIFIED value + * @property {number} EXEC_TRY=1 EXEC_TRY value + */ + v1.Exec = (function () { + var valuesById = {}, + values = Object.create(valuesById); + values[(valuesById[0] = 'EXEC_UNSPECIFIED')] = 0; + values[(valuesById[1] = 'EXEC_TRY')] = 1; + return values; + })(); + + return v1; + })(); + + return group; + })(); + + return cosmos; +})(); + +$root.google = (function () { + /** + * Namespace google. + * @exports google + * @namespace + */ + var google = {}; + + google.protobuf = (function () { + /** + * Namespace protobuf. + * @memberof google + * @namespace + */ + var protobuf = {}; + + protobuf.Any = (function () { + /** + * Properties of an Any. + * @memberof google.protobuf + * @interface IAny + * @property {string|null} [type_url] Any type_url + * @property {Uint8Array|null} [value] Any value + */ + + /** + * Constructs a new Any. + * @memberof google.protobuf + * @classdesc Represents an Any. + * @implements IAny + * @constructor + * @param {google.protobuf.IAny=} [properties] Properties to set + */ + function Any(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) this[keys[i]] = properties[keys[i]]; + } + + /** + * Any type_url. + * @member {string} type_url + * @memberof google.protobuf.Any + * @instance + */ + Any.prototype.type_url = ''; + + /** + * Any value. + * @member {Uint8Array} value + * @memberof google.protobuf.Any + * @instance + */ + Any.prototype.value = $util.newBuffer([]); + + /** + * Creates a new Any instance using the specified properties. + * @function create + * @memberof google.protobuf.Any + * @static + * @param {google.protobuf.IAny=} [properties] Properties to set + * @returns {google.protobuf.Any} Any instance + */ + Any.create = function create(properties) { + return new Any(properties); + }; + + /** + * Encodes the specified Any message. Does not implicitly {@link google.protobuf.Any.verify|verify} messages. + * @function encode + * @memberof google.protobuf.Any + * @static + * @param {google.protobuf.IAny} message Any message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Any.encode = function encode(message, writer) { + if (!writer) writer = $Writer.create(); + if (message.type_url != null && Object.hasOwnProperty.call(message, 'type_url')) + writer.uint32(/* id 1, wireType 2 =*/ 10).string(message.type_url); + if (message.value != null && Object.hasOwnProperty.call(message, 'value')) + writer.uint32(/* id 2, wireType 2 =*/ 18).bytes(message.value); + return writer; + }; + + /** + * Encodes the specified Any message, length delimited. Does not implicitly {@link google.protobuf.Any.verify|verify} messages. + * @function encodeDelimited + * @memberof google.protobuf.Any + * @static + * @param {google.protobuf.IAny} message Any message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Any.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes an Any message from the specified reader or buffer. + * @function decode + * @memberof google.protobuf.Any + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {google.protobuf.Any} Any + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Any.decode = function decode(reader, length, error) { + if (!(reader instanceof $Reader)) reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.google.protobuf.Any(); + while (reader.pos < end) { + var tag = reader.uint32(); + if (tag === error) break; + switch (tag >>> 3) { + case 1: { + message.type_url = reader.string(); + break; + } + case 2: { + message.value = reader.bytes(); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes an Any message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof google.protobuf.Any + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {google.protobuf.Any} Any + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Any.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies an Any message. + * @function verify + * @memberof google.protobuf.Any + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + Any.verify = function verify(message) { + if (typeof message !== 'object' || message === null) return 'object expected'; + if (message.type_url != null && message.hasOwnProperty('type_url')) + if (!$util.isString(message.type_url)) return 'type_url: string expected'; + if (message.value != null && message.hasOwnProperty('value')) + if (!((message.value && typeof message.value.length === 'number') || $util.isString(message.value))) + return 'value: buffer expected'; + return null; + }; + + /** + * Creates an Any message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof google.protobuf.Any + * @static + * @param {Object.} object Plain object + * @returns {google.protobuf.Any} Any + */ + Any.fromObject = function fromObject(object) { + if (object instanceof $root.google.protobuf.Any) return object; + var message = new $root.google.protobuf.Any(); + if (object.type_url != null) message.type_url = String(object.type_url); + if (object.value != null) + if (typeof object.value === 'string') + $util.base64.decode(object.value, (message.value = $util.newBuffer($util.base64.length(object.value))), 0); + else if (object.value.length >= 0) message.value = object.value; + return message; + }; + + /** + * Creates a plain object from an Any message. Also converts values to other types if specified. + * @function toObject + * @memberof google.protobuf.Any + * @static + * @param {google.protobuf.Any} message Any + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + Any.toObject = function toObject(message, options) { + if (!options) options = {}; + var object = {}; + if (options.defaults) { + object.type_url = ''; + if (options.bytes === String) object.value = ''; + else { + object.value = []; + if (options.bytes !== Array) object.value = $util.newBuffer(object.value); + } + } + if (message.type_url != null && message.hasOwnProperty('type_url')) object.type_url = message.type_url; + if (message.value != null && message.hasOwnProperty('value')) + object.value = + options.bytes === String + ? $util.base64.encode(message.value, 0, message.value.length) + : options.bytes === Array + ? Array.prototype.slice.call(message.value) + : message.value; + return object; + }; + + /** + * Converts this Any to JSON. + * @function toJSON + * @memberof google.protobuf.Any + * @instance + * @returns {Object.} JSON object + */ + Any.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for Any + * @function getTypeUrl + * @memberof google.protobuf.Any + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + Any.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = 'type.googleapis.com'; + } + return typeUrlPrefix + '/google.protobuf.Any'; + }; + + return Any; + })(); + + return protobuf; + })(); + + return google; +})(); + +module.exports = $root; diff --git a/modules/abstract-cosmos/src/lib/ContractCallBuilder.ts b/modules/abstract-cosmos/src/lib/ContractCallBuilder.ts index 1fd775cf32..7791b16372 100644 --- a/modules/abstract-cosmos/src/lib/ContractCallBuilder.ts +++ b/modules/abstract-cosmos/src/lib/ContractCallBuilder.ts @@ -1,8 +1,9 @@ import { TransactionType } from '@bitgo/sdk-core'; import { BaseCoin as CoinConfig } from '@bitgo/statics'; +import { fromBase64 } from '@cosmjs/encoding'; import * as constants from './constants'; -import { ExecuteContractMessage } from './iface'; +import { CosmosTransactionMessage, ExecuteContractMessage, MessageData } from './iface'; import { CosmosTransactionBuilder } from './transactionBuilder'; import { CosmosUtils } from './utils'; @@ -19,8 +20,27 @@ export class ContractCallBuilder extends CosmosTransactio } /** @inheritdoc */ - messages(messages: ExecuteContractMessage[]): this { - this._messages = messages.map((executeContractMessage) => { + messages(messages: (CosmosTransactionMessage | MessageData)[]): this { + this._messages = messages.map((message) => { + const msg = message as MessageData; + const { typeUrl, value } = msg; + + // Handle pre-encoded messages (base64 string input) + if (typeUrl && typeof value === 'string') { + try { + return { typeUrl, value: fromBase64(value) } as MessageData; + } catch (err: unknown) { + throw new Error(`Invalid base64 string in message value: ${String(err)}`); + } + } + + // Handle already-encoded messages (Uint8Array from deserialization) + if (typeUrl && value instanceof Uint8Array) { + return { typeUrl, value } as MessageData; + } + + // Handle typed ExecuteContractMessage + const executeContractMessage = message as ExecuteContractMessage; this._utils.validateExecuteContractMessage(executeContractMessage, this.transactionType); return { typeUrl: constants.executeContractMsgTypeUrl, diff --git a/modules/abstract-cosmos/src/lib/constants.ts b/modules/abstract-cosmos/src/lib/constants.ts index b8c7b233aa..35e5461511 100644 --- a/modules/abstract-cosmos/src/lib/constants.ts +++ b/modules/abstract-cosmos/src/lib/constants.ts @@ -5,6 +5,7 @@ export const undelegateMsgTypeUrl = '/cosmos.staking.v1beta1.MsgUndelegate'; export const redelegateTypeUrl = '/cosmos.staking.v1beta1.MsgBeginRedelegate'; export const withdrawDelegatorRewardMsgTypeUrl = '/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward'; export const executeContractMsgTypeUrl = '/cosmwasm.wasm.v1.MsgExecuteContract'; +export const groupProposalMsgTypeUrl = '/cosmos.group.v1.MsgSubmitProposal'; export const UNAVAILABLE_TEXT = 'UNAVAILABLE'; export const ROOT_PATH = 'm/0'; export const sendMsgType = '/types.MsgSend'; // thorchain uses this custom message type diff --git a/modules/abstract-cosmos/src/lib/iface.ts b/modules/abstract-cosmos/src/lib/iface.ts index d0154b1bde..c21d1456b7 100644 --- a/modules/abstract-cosmos/src/lib/iface.ts +++ b/modules/abstract-cosmos/src/lib/iface.ts @@ -78,7 +78,7 @@ export type CosmosTransactionMessage = export interface MessageData { typeUrl: string; - value: CosmosTransactionMessage; + value: CosmosTransactionMessage | Uint8Array; // Uint8Array for pre-encoded messages } export interface FeeData { diff --git a/modules/abstract-cosmos/src/lib/protobuf-init.ts b/modules/abstract-cosmos/src/lib/protobuf-init.ts new file mode 100644 index 0000000000..d2c5fdd9f7 --- /dev/null +++ b/modules/abstract-cosmos/src/lib/protobuf-init.ts @@ -0,0 +1,25 @@ +/** + * Initialize and patch protobuf modules to ensure proper namespace binding + */ +export function initializeProtobufModules(): { MsgCompiled: any; ProposalCompiled: any; unifiedRoot: any } { + const MsgCompiled = require('../../resources/MsgCompiled'); + + // Store cosmos.base before loading ProposalCompiled (both modules share the same $root object) + const cosmosBase = MsgCompiled.cosmos?.base; + const ProposalCompiled = require('../../resources/ProposalCompiled'); + + // Fix namespace collision by restoring cosmos.base + if (cosmosBase && !MsgCompiled.cosmos.base) { + MsgCompiled.cosmos.base = cosmosBase; + } + + if (ProposalCompiled.cosmos?.group && !MsgCompiled.cosmos.group) { + MsgCompiled.cosmos.group = ProposalCompiled.cosmos.group; + } + + return { + MsgCompiled, + ProposalCompiled, + unifiedRoot: MsgCompiled, + }; +} diff --git a/modules/abstract-cosmos/src/lib/transaction.ts b/modules/abstract-cosmos/src/lib/transaction.ts index 1e812dde13..4c99660434 100644 --- a/modules/abstract-cosmos/src/lib/transaction.ts +++ b/modules/abstract-cosmos/src/lib/transaction.ts @@ -228,6 +228,13 @@ export class CosmosTransaction extends BaseTransaction { explanationResult.type = TransactionType.ContractCall; outputAmount = BigInt(0); outputs = json.sendMessages.map((message) => { + //TODO: Handle pre-encoded contract call messages. + if (message.value instanceof Uint8Array) { + return { + address: UNAVAILABLE_TEXT, + amount: UNAVAILABLE_TEXT, + }; + } const executeContractMessage = message.value as ExecuteContractMessage; outputAmount = outputAmount + BigInt(executeContractMessage.funds?.[0]?.amount ?? '0'); return { @@ -324,6 +331,20 @@ export class CosmosTransaction extends BaseTransaction { break; case TransactionType.ContractCall: this.cosmosLikeTransaction.sendMessages.forEach((message) => { + if (message.value instanceof Uint8Array) { + //TODO: Handle pre-encoded contract call messages. + inputs.push({ + address: UNAVAILABLE_TEXT, + value: UNAVAILABLE_TEXT, + coin: this._coinConfig.name, + }); + outputs.push({ + address: UNAVAILABLE_TEXT, + value: UNAVAILABLE_TEXT, + coin: this._coinConfig.name, + }); + return; + } const executeContractMessage = message.value as ExecuteContractMessage; inputs.push({ address: executeContractMessage.sender, diff --git a/modules/abstract-cosmos/src/lib/transactionBuilder.ts b/modules/abstract-cosmos/src/lib/transactionBuilder.ts index 109a49231e..bb57666ea0 100644 --- a/modules/abstract-cosmos/src/lib/transactionBuilder.ts +++ b/modules/abstract-cosmos/src/lib/transactionBuilder.ts @@ -75,10 +75,10 @@ export abstract class CosmosTransactionBuilder extends Ba * - For @see TransactionType.Send required type is @see SendMessage * - For @see TransactionType.StakingWithdraw required type is @see WithdrawDelegatorRewardsMessage * - For @see TransactionType.ContractCall required type is @see ExecuteContractMessage - * @param {CosmosTransactionMessage[]} messages + * @param {CosmosTransactionMessage[] | MessageData[]} messages * @returns {TransactionBuilder} This transaction builder */ - abstract messages(messages: CosmosTransactionMessage[]): this; + abstract messages(messages: (CosmosTransactionMessage | MessageData)[]): this; publicKey(publicKey: string | undefined): this { this._publicKey = publicKey; @@ -166,11 +166,15 @@ export abstract class CosmosTransactionBuilder extends Ba this._transaction = tx; const txData = tx.toJson(); this.gasBudget(txData.gasBudget); - this.messages( + const messagesToSet: (MessageData | CosmosTransactionMessage)[] = txData.sendMessages.map((message) => { - return message.value; - }) - ); + if (message.value instanceof Uint8Array) { + return message; // Keep as MessageData for pre-encoded messages + } else { + return message.value; // Extract the actual message for typed messages + } + }); + this.messages(messagesToSet); this.sequence(txData.sequence); this.publicKey(txData.publicKey); this.accountNumber(txData.accountNumber); diff --git a/modules/abstract-cosmos/src/lib/utils.ts b/modules/abstract-cosmos/src/lib/utils.ts index 7c5c5f1818..dc6820e443 100644 --- a/modules/abstract-cosmos/src/lib/utils.ts +++ b/modules/abstract-cosmos/src/lib/utils.ts @@ -37,8 +37,11 @@ import { WithdrawDelegatorRewardsMessage, } from './iface'; import { CosmosKeyPair as KeyPair } from './keyPair'; +import { initializeProtobufModules } from './protobuf-init'; -const { MsgSend } = require('../../resources/MsgCompiled').types; +const { MsgCompiled, ProposalCompiled } = initializeProtobufModules(); +const { MsgSend } = MsgCompiled.types; +const { MsgSubmitProposal } = ProposalCompiled.cosmos.group.v1; export class CosmosUtils implements BaseUtils { protected registry; @@ -47,6 +50,7 @@ export class CosmosUtils implements BaseUtils { this.registry = new Registry([...defaultRegistryTypes]); this.registry.register(constants.executeContractMsgTypeUrl, MsgExecuteContract); this.registry.register('/types.MsgSend', MsgSend); + this.registry.register(constants.groupProposalMsgTypeUrl, MsgSubmitProposal); } /** @inheritdoc */ @@ -326,16 +330,27 @@ export class CosmosUtils implements BaseUtils { */ getExecuteContractMessageDataFromDecodedTx(decodedTx: DecodedTxRaw): MessageData[] { return decodedTx.body.messages.map((message) => { - const value = this.registry.decode(message); - return { - value: { - sender: value.sender, - contract: value.contract, - msg: value.msg, - funds: value.funds, - }, - typeUrl: message.typeUrl, - }; + if (message.typeUrl !== constants.groupProposalMsgTypeUrl) { + try { + const value = this.registry.decode(message); + return { + value: { + sender: value.sender, + contract: value.contract, + msg: value.msg, + funds: value.funds, + }, + typeUrl: message.typeUrl, + }; + } catch (error) { + throw new ParseTransactionError(`Error decoding execute contract message: ${error.message}`); + } + } else { + return { + value: message.value, + typeUrl: message.typeUrl, + } as MessageData; + } }); } @@ -367,6 +382,8 @@ export class CosmosUtils implements BaseUtils { return TransactionType.StakingWithdraw; case constants.executeContractMsgTypeUrl: return TransactionType.ContractCall; + case constants.groupProposalMsgTypeUrl: + return TransactionType.ContractCall; case constants.redelegateTypeUrl: return TransactionType.StakingRedelegate; default: @@ -390,7 +407,33 @@ export class CosmosUtils implements BaseUtils { * @returns {Any[]} processed send messages */ getSendMessagesForEncodingTx(cosmosLikeTransaction: CosmosLikeTransaction): Any[] { - return cosmosLikeTransaction.sendMessages as unknown as Any[]; + return cosmosLikeTransaction.sendMessages.map((msg) => { + if (msg.value instanceof Uint8Array) { + if (msg.typeUrl === constants.groupProposalMsgTypeUrl) { + // For group proposal messages, the pre-encoded bytes contain the full MsgSubmitProposal + try { + const decoded = this.registry.decode({ typeUrl: msg.typeUrl, value: msg.value }); + return { + typeUrl: msg.typeUrl, + value: decoded, + } as Any; + } catch (error) { + // If decoding fails, fall back to returning the bytes directly + return { + typeUrl: msg.typeUrl, + value: msg.value, + } as Any; + } + } else { + return { + typeUrl: msg.typeUrl, + value: msg.value, + } as Any; + } + } else { + return msg as unknown as Any; + } + }); } /** @@ -637,6 +680,10 @@ export class CosmosUtils implements BaseUtils { break; } case TransactionType.ContractCall: { + if (messageData.value instanceof Uint8Array) { + //TODO: Add validation for pre-encoded contract call messages + break; + } const value = messageData.value as ExecuteContractMessage; this.validateExecuteContractMessage(value, TransactionType.ContractCall); break; diff --git a/modules/sdk-coin-hash/test/resources/hash.ts b/modules/sdk-coin-hash/test/resources/hash.ts index f7cc21cdb4..c6fecd2d9c 100644 --- a/modules/sdk-coin-hash/test/resources/hash.ts +++ b/modules/sdk-coin-hash/test/resources/hash.ts @@ -1,5 +1,26 @@ // Get the test data by running the scripts for the particular coin from coin-sandbox repo. +export const TEST_CONTRACT_CALL = { + //pre generated message using cosmos sdk and figure market apis + preEncodedMessageValue: + 'Ci0vcHJvdmVuYW5jZS5leGNoYW5nZS52MS5Nc2dDb21taXRGdW5kc1JlcXVlc3QSjQEKPXRwMXRhemVmd2syZTM3MmZ5MmpxMDh3Nmx6dGc5eXJydmM0OTByMmdwNHZ0OGQwZmNobHJmcXF5YWhnMHUQARoUCgl1eWxkcy5mY2MSBzEwMDAwMDAqNGV4Y2hhbmdlLWNvbW1pdDoyMWVhNjM0MC05OWFmLTRjOGMtOGZhMC03NWZlMGQxYmJkNDQ=', + encodedProposal: + 'Cj10cDF0YXplZndrMmUzNzJmeTJqcTA4dzZsenRnOXlycnZjNDkwcjJncDR2dDhkMGZjaGxyZnFxeWFoZzB1Eil0cDEybnluODN5bmV3dG1wa3czMndxNmRnODN3eDhucXBhdDY1Z2NsZBoPZXhjaGFuZ2UtY29tbWl0Ir8BCi0vcHJvdmVuYW5jZS5leGNoYW5nZS52MS5Nc2dDb21taXRGdW5kc1JlcXVlc3QSjQEKPXRwMXRhemVmd2syZTM3MmZ5MmpxMDh3Nmx6dGc5eXJydmM0OTByMmdwNHZ0OGQwZmNobHJmcXF5YWhnMHUQARoUCgl1eWxkcy5mY2MSBzEwMDAwMDAqNGV4Y2hhbmdlLWNvbW1pdDoyMWVhNjM0MC05OWFmLTRjOGMtOGZhMC03NWZlMGQxYmJkNDQoAQ==', + pubKey: 'A58Dr1SAmP95RFbQXyrcw4nPwEq8bhbZJmmmJV4zFFsh', + privateKey: 'sW1/vJr2qhN8MtrM8oK6xGKDTBo1VxMdLEP4yJzcEz4=', + feeGranter: 'tp12vdnr7ddckx0m8u62qusrzq5r66cej5rd49zwf', + proposer: 'tp12nyn83ynewtmpkw32wq6dg83wx8nqpat65gcld', + chainId: 'pio-testnet-1', + accountNumber: 239218, + sequence: 16, + fee: '40000000000', + gasLimit: 250000, + messageTypeUrl: '/cosmos.group.v1.MsgSubmitProposal', + //pre generated message using cosmos sdk + expectedSignBytesHex: + '0ae9020ae6020a222f636f736d6f732e67726f75702e76312e4d73675375626d697450726f706f73616c12bf020a3d74703174617a6566776b32653337326679326a71303877366c7a7467397972727663343930723267703476743864306663686c726671717961686730751229747031326e796e3833796e6577746d706b773332777136646738337778386e71706174363567636c641a0f65786368616e67652d636f6d6d697422bf010a2d2f70726f76656e616e63652e65786368616e67652e76312e4d7367436f6d6d697446756e647352657175657374128d010a3d74703174617a6566776b32653337326679326a71303877366c7a7467397972727663343930723267703476743864306663686c7266717179616867307510011a140a0975796c64732e6663631207313030303030302a3465786368616e67652d636f6d6d69743a32316561363334302d393961662d346338632d386661302d37356665306431626264343428011299010a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21039f03af548098ff794456d05f2adcc389cfc04abc6e16d92669a6255e33145b2112040a020801181012450a140a056e68617368120b34303030303030303030301090a10f22297470313276646e72376464636b78306d38753632717573727a713572363663656a35726434397a77661a0d70696f2d746573746e65742d3120f2cc0e', +}; + export const TEST_ACCOUNT = { pubAddress: 'pb1496r8u4a48k6khknrhzd6c8cm3c64ewxhlyxpc', testnetPubAddress: 'tp1496r8u4a48k6khknrhzd6c8cm3c64ewxy5p2rj', diff --git a/modules/sdk-coin-hash/test/unit/transactionBuilder/contractCallBuilder.ts b/modules/sdk-coin-hash/test/unit/transactionBuilder/contractCallBuilder.ts new file mode 100644 index 0000000000..e6cf78b25a --- /dev/null +++ b/modules/sdk-coin-hash/test/unit/transactionBuilder/contractCallBuilder.ts @@ -0,0 +1,99 @@ +import { BitGoAPI } from '@bitgo/sdk-api'; +import { TransactionType } from '@bitgo/sdk-core'; +import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test'; +import { fromBase64, toHex } from '@cosmjs/encoding'; +import should from 'should'; +import { Hash, Thash } from '../../../src'; +import { TEST_CONTRACT_CALL } from '../../resources/hash'; + +describe('Hash ContractCall Builder', () => { + let bitgo: TestBitGoAPI; + let basecoin; + let factory; + + // Helper function to create a complete transaction builder with standard settings and message + const contractCallBuilder = () => { + const txBuilder = factory.getContractCallBuilder(); + txBuilder.sequence(TEST_CONTRACT_CALL.sequence); + txBuilder.accountNumber(TEST_CONTRACT_CALL.accountNumber); + txBuilder.chainId(TEST_CONTRACT_CALL.chainId); + txBuilder.gasBudget({ + amount: [{ denom: 'nhash', amount: TEST_CONTRACT_CALL.fee }], + gasLimit: TEST_CONTRACT_CALL.gasLimit, + }); + txBuilder.feeGranter(TEST_CONTRACT_CALL.feeGranter); + txBuilder.publicKey(toHex(fromBase64(TEST_CONTRACT_CALL.pubKey))); + + // Wrap the inner message in a group proposal + // const wrappedMessage = wrapInGroupProposal( + // TEST_CONTRACT_CALL.preEncodedMessageValue, + // TEST_CONTRACT_CALL.proposer, + // testnetAddress.groupPolicyAddress + // ); + + txBuilder.messages([ + { + typeUrl: '/cosmos.group.v1.MsgSubmitProposal', + value: TEST_CONTRACT_CALL.encodedProposal, + }, + ]); + return txBuilder; + }; + + before(function () { + bitgo = TestBitGo.decorate(BitGoAPI, { env: 'mock' }); + bitgo.safeRegister('hash', Hash.createInstance); + bitgo.safeRegister('thash', Thash.createInstance); + bitgo.initializeTestVars(); + basecoin = bitgo.coin('thash'); + factory = basecoin.getBuilder(); + }); + + describe('Contract Call Builder Tests', () => { + it('should build transaction with expected signable payload', async function () { + const txBuilder = contractCallBuilder(); + const tx = await txBuilder.build(); + should.equal(toHex(tx.signablePayload), TEST_CONTRACT_CALL.expectedSignBytesHex); + should.equal(tx.type, TransactionType.ContractCall); + }); + + it('should build, sign, and serialize contract call transactions', async function () { + // Test unsigned transaction building + const unsignedBuilder = contractCallBuilder(); + const unsignedTx = await unsignedBuilder.build(); + should.equal(unsignedTx.type, TransactionType.ContractCall); + should.equal(unsignedTx.signature.length, 0); + should.exist(unsignedTx.toBroadcastFormat()); + + // Test signing functionality + const signedBuilder = contractCallBuilder(); + signedBuilder.sign({ key: toHex(fromBase64(TEST_CONTRACT_CALL.privateKey)) }); + const signedTx = await signedBuilder.build(); + should.equal(signedTx.type, TransactionType.ContractCall); + should.equal(signedTx.signature.length, 1); + should.exist(signedTx.toBroadcastFormat()); + }); + + it('should handle round-trip serialization for signed and unsigned transactions', async function () { + // Test unsigned serialization + const unsignedBuilder = contractCallBuilder(); + const unsignedTx = await unsignedBuilder.build(); + const unsignedRaw = unsignedTx.toBroadcastFormat(); + const rebuiltUnsigned = factory.from(unsignedRaw); + const rebuiltUnsignedTx = await rebuiltUnsigned.build(); + should.equal(rebuiltUnsignedTx.toBroadcastFormat(), unsignedRaw); + should.equal(rebuiltUnsignedTx.type, TransactionType.ContractCall); + + // Test signed serialization + const signedBuilder = contractCallBuilder(); + signedBuilder.sign({ key: toHex(fromBase64(TEST_CONTRACT_CALL.privateKey)) }); + const signedTx = await signedBuilder.build(); + const signedRaw = signedTx.toBroadcastFormat(); + const rebuiltSigned = factory.from(signedRaw); + const rebuiltSignedTx = await rebuiltSigned.build(); + should.equal(rebuiltSignedTx.toBroadcastFormat(), signedRaw); + should.equal(rebuiltSignedTx.signature.length, 1); + should.equal(rebuiltSignedTx.signature[0], signedTx.signature[0]); + }); + }); +});