From 3856316b5d7a7d40a57c506e643120ec2d5314f4 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 23 May 2025 11:25:16 +0300 Subject: [PATCH 1/3] feat: PoC on sign 2.5 methods --- packages/core/src/constants/relayer.ts | 2 +- packages/core/src/controllers/pairing.ts | 5 +- packages/core/src/controllers/publisher.ts | 193 +++++++++------- packages/core/src/controllers/relayer.ts | 12 +- packages/core/src/controllers/subscriber.ts | 30 ++- packages/core/src/core.ts | 5 +- .../sign-client/src/controllers/engine.ts | 215 +++++++++++++++--- packages/sign-client/test/sdk/client.spec.ts | 8 +- packages/types/src/core/pairing.ts | 1 + packages/types/src/core/publisher.ts | 5 + packages/types/src/core/relayer.ts | 7 + packages/types/src/sign-client/engine.ts | 32 +++ 12 files changed, 387 insertions(+), 128 deletions(-) diff --git a/packages/core/src/constants/relayer.ts b/packages/core/src/constants/relayer.ts index 36bdf0ac56..09d2210b3a 100644 --- a/packages/core/src/constants/relayer.ts +++ b/packages/core/src/constants/relayer.ts @@ -2,7 +2,7 @@ export const RELAYER_DEFAULT_PROTOCOL = "irn"; export const RELAYER_DEFAULT_LOGGER = "error"; -export const RELAYER_DEFAULT_RELAY_URL = "wss://relay.walletconnect.org"; +export const RELAYER_DEFAULT_RELAY_URL = "wss://staging-relay.walletconnect.com"; export const RELAYER_CONTEXT = "relayer"; diff --git a/packages/core/src/controllers/pairing.ts b/packages/core/src/controllers/pairing.ts index 80228bcb1e..56ec4a4216 100644 --- a/packages/core/src/controllers/pairing.ts +++ b/packages/core/src/controllers/pairing.ts @@ -112,7 +112,10 @@ export class Pairing implements IPairing { this.events.emit(PAIRING_EVENTS.create, pairing); this.core.expirer.set(topic, expiry); await this.pairings.set(topic, pairing); - await this.core.relayer.subscribe(topic, { transportType: params?.transportType }); + await this.core.relayer.subscribe(topic, { + transportType: params?.transportType, + internal: params?.internal, + }); return { topic, uri }; }; diff --git a/packages/core/src/controllers/publisher.ts b/packages/core/src/controllers/publisher.ts index c2cbcc800c..62760e62b6 100644 --- a/packages/core/src/controllers/publisher.ts +++ b/packages/core/src/controllers/publisher.ts @@ -1,8 +1,9 @@ +/* eslint-disable no-console */ import { HEARTBEAT_EVENTS } from "@walletconnect/heartbeat"; import { JsonRpcPayload, RequestArguments } from "@walletconnect/jsonrpc-types"; import { generateChildLogger, getLoggerContext, Logger } from "@walletconnect/logger"; import { RelayJsonRpc } from "@walletconnect/relay-api"; -import { IPublisher, IRelayer, PublisherTypes, RelayerTypes } from "@walletconnect/types"; +import { IPublisher, IRelayer, RelayerTypes } from "@walletconnect/types"; import { getRelayProtocolApi, getRelayProtocolName, @@ -13,12 +14,15 @@ import { EventEmitter } from "events"; import { PUBLISHER_CONTEXT, PUBLISHER_DEFAULT_TTL, RELAYER_EVENTS } from "../constants"; import { getBigIntRpcId } from "@walletconnect/jsonrpc-utils"; -import { ONE_MINUTE, ONE_SECOND, toMiliseconds } from "@walletconnect/time"; +import { FIVE_MINUTES, ONE_MINUTE, ONE_SECOND, toMiliseconds } from "@walletconnect/time"; -type IPublishType = PublisherTypes.Params & { +type IPublishType = { attestation?: string; attempt: number; + request: RequestArguments; + opts?: RelayerTypes.PublishOptions; }; + export class Publisher extends IPublisher { public events = new EventEmitter(); public name = PUBLISHER_CONTEXT; @@ -47,52 +51,49 @@ export class Publisher extends IPublisher { this.logger.trace({ type: "method", method: "publish", params: { topic, message, opts } }); const ttl = opts?.ttl || PUBLISHER_DEFAULT_TTL; - const relay = getRelayProtocolName(opts); const prompt = opts?.prompt || false; const tag = opts?.tag || 0; const id = opts?.id || (getBigIntRpcId().toString() as any); - const params = { - topic, - message, - opts: { + + const api = getRelayProtocolApi(getRelayProtocolName().protocol); + + const request: RequestArguments = { + id, + method: opts?.publishMethod || api.publish, + params: { + topic, + message, ttl, - relay, prompt, tag, - id, attestation: opts?.attestation, - tvf: opts?.tvf, + ...opts?.tvf, }, }; - const failedPublishMessage = `Failed to publish payload, please try again. id:${id} tag:${tag}`; + const failedPublishMessage = `Failed to publish payload, please try again. id:${id} tag:${tag}`; + console.log("publish params", opts); try { + if (isUndefined(request.params?.prompt)) delete request.params?.prompt; + if (isUndefined(request.params?.tag)) delete request.params?.tag; + /** * attempt to publish the payload for seconds, * if the publish fails, add the payload to the queue and it will be retried on every pulse * until it is successfully published or seconds have passed */ - const publishPromise = new Promise(async (resolve) => { + const publishPromise = new Promise(async (resolve) => { const onPublish = ({ id }: { id: string }) => { - if (params.opts.id === id) { + if (request.id?.toString() === id.toString()) { this.removeRequestFromQueue(id); this.relayer.events.removeListener(RELAYER_EVENTS.publish, onPublish); - resolve(params); + resolve(); } }; this.relayer.events.on(RELAYER_EVENTS.publish, onPublish); const initialPublish = createExpiringPromise( new Promise((resolve, reject) => { - this.rpcPublish({ - topic, - message, - ttl, - prompt, - tag, - id, - attestation: opts?.attestation, - tvf: opts?.tvf, - }) + this.rpcPublish(request, opts) .then(resolve) .catch((e) => { this.logger.warn(e, e?.message); @@ -106,7 +107,7 @@ export class Publisher extends IPublisher { await initialPublish; this.events.removeListener(RELAYER_EVENTS.publish, onPublish); } catch (e) { - this.queue.set(id, { ...params, attempt: 1 }); + this.queue.set(id, { request, opts, attempt: 1 }); this.logger.warn(e, (e as Error)?.message); } }); @@ -128,6 +129,87 @@ export class Publisher extends IPublisher { } }; + public publishCustom: IPublisher["publishCustom"] = async (params) => { + this.logger.debug(`Publishing custom payload`); + this.logger.trace({ type: "method", method: "publishCustom", params }); + + const { payload, opts = {} } = params; + const { attestation, tvf, publishMethod, prompt, tag, ttl = FIVE_MINUTES } = opts; + + const id = opts.id || (getBigIntRpcId().toString() as any); + const api = getRelayProtocolApi(getRelayProtocolName().protocol); + const method = publishMethod || api.publish; + const request: RequestArguments = { + id, + method, + params: { + ...payload, + ttl, + prompt, + tag, + attestation, + ...tvf, + }, + }; + console.log("publishCustom request", request); + const failedPublishMessage = `Failed to publish custom payload, please try again. id:${id} tag:${tag}`; + console.log("publish params", opts); + try { + if (isUndefined(request.params?.prompt)) delete request.params?.prompt; + if (isUndefined(request.params?.tag)) delete request.params?.tag; + + /** + * attempt to publish the payload for seconds, + * if the publish fails, add the payload to the queue and it will be retried on every pulse + * until it is successfully published or seconds have passed + */ + const publishPromise = new Promise(async (resolve) => { + const onPublish = ({ id }: { id: string }) => { + if (request.id?.toString() === id.toString()) { + this.removeRequestFromQueue(id); + this.relayer.events.removeListener(RELAYER_EVENTS.publish, onPublish); + resolve(); + } + }; + this.relayer.events.on(RELAYER_EVENTS.publish, onPublish); + const initialPublish = createExpiringPromise( + new Promise((resolve, reject) => { + this.rpcPublish(request, opts) + .then(resolve) + .catch((e) => { + this.logger.warn(e, e?.message); + reject(e); + }); + }), + this.initialPublishTimeout, + `Failed initial custom payload publish, retrying.... method:${method} id:${id} tag:${tag}`, + ); + try { + await initialPublish; + this.events.removeListener(RELAYER_EVENTS.publish, onPublish); + } catch (e) { + this.queue.set(id, { request, opts, attempt: 1 }); + this.logger.warn(e, (e as Error)?.message); + } + }); + this.logger.trace({ + type: "method", + method: "publish", + params: { id, payload, opts }, + }); + + await createExpiringPromise(publishPromise, this.publishTimeout, failedPublishMessage); + } catch (e) { + this.logger.debug(`Failed to Publish Payload`); + this.logger.error(e as any); + if (opts?.internal?.throwOnFailedPublish) { + throw e; + } + } finally { + this.queue.delete(id); + } + }; + public on: IPublisher["on"] = (event, listener) => { this.events.on(event, listener); }; @@ -146,46 +228,14 @@ export class Publisher extends IPublisher { // ---------- Private ----------------------------------------------- // - private async rpcPublish(params: { - topic: string; - message: string; - ttl?: number; - prompt?: boolean; - tag?: number; - id?: number; - attestation?: string; - tvf?: RelayerTypes.ITVF; - }) { - const { - topic, - message, - ttl = PUBLISHER_DEFAULT_TTL, - prompt, - tag, - id, - attestation, - tvf, - } = params; - const api = getRelayProtocolApi(getRelayProtocolName().protocol); - const request: RequestArguments = { - method: api.publish, - params: { - topic, - message, - ttl, - prompt, - tag, - attestation, - ...tvf, - }, - id, - }; - if (isUndefined(request.params?.prompt)) delete request.params?.prompt; - if (isUndefined(request.params?.tag)) delete request.params?.tag; + private async rpcPublish(request: RequestArguments, opts?: RelayerTypes.PublishOptions) { + console.log("rpcPublish params", request, globalThis.CoreId); this.logger.debug(`Outgoing Relay Payload`); this.logger.trace({ type: "message", direction: "outgoing", request }); const result = await this.relayer.request(request); - this.relayer.events.emit(RELAYER_EVENTS.publish, params); + console.log("rpcPublish result", result); + + this.relayer.events.emit(RELAYER_EVENTS.publish, { ...request, ...opts }); this.logger.debug(`Successfully Published Payload`); return result; } @@ -198,23 +248,12 @@ export class Publisher extends IPublisher { this.queue.forEach(async (params, id) => { const attempt = params.attempt + 1; this.queue.set(id, { ...params, attempt }); - const { topic, message, opts, attestation } = params; this.logger.warn( {}, - `Publisher: queue->publishing: ${params.opts.id}, tag: ${params.opts.tag}, attempt: ${attempt}`, + `Publisher: queue->publishing: ${params.request.id}, tag: ${params.request.params?.tag}, attempt: ${attempt}`, ); - await this.rpcPublish({ - ...params, - topic, - message, - ttl: opts.ttl, - prompt: opts.prompt, - tag: opts.tag, - id: opts.id, - attestation, - tvf: opts.tvf, - }); - this.logger.warn({}, `Publisher: queue->published: ${params.opts.id}`); + await this.rpcPublish(params.request, params.opts); + this.logger.warn({}, `Publisher: queue->published: ${params.request.id}`); }); } diff --git a/packages/core/src/controllers/relayer.ts b/packages/core/src/controllers/relayer.ts index dd2547416f..d50f1dabaa 100644 --- a/packages/core/src/controllers/relayer.ts +++ b/packages/core/src/controllers/relayer.ts @@ -113,8 +113,8 @@ export class Relayer extends IRelayer { this.subscriber = new Subscriber(this, this.logger); this.publisher = new Publisher(this, this.logger); - this.relayUrl = opts?.relayUrl || RELAYER_DEFAULT_RELAY_URL; - this.projectId = opts.projectId; + this.relayUrl = RELAYER_DEFAULT_RELAY_URL; + this.projectId = "47a263e9e957d954353cb970f024e1d3"; if (isAndroid()) { this.packageName = getAppId(); @@ -173,6 +173,11 @@ export class Relayer extends IRelayer { ); } + public async publishCustom(params: { payload: any; opts?: RelayerTypes.PublishOptions }) { + this.isInitialized(); + await this.publisher.publishCustom(params); + } + public async subscribe(topic: string, opts?: RelayerTypes.SubscribeOptions) { this.isInitialized(); if (!opts?.transportType || opts?.transportType === "relay") { @@ -281,7 +286,8 @@ export class Relayer extends IRelayer { async transportOpen(relayUrl?: string) { if (!this.subscriber.hasAnyTopics) { this.logger.warn( - "Starting WS connection skipped because the client has no topics to work with.", + "Starting WS connection skipped because the client has no topics to work with." + + globalThis.CoreId, ); return; } diff --git a/packages/core/src/controllers/subscriber.ts b/packages/core/src/controllers/subscriber.ts index 22dbc9d804..8c1eec55e0 100644 --- a/packages/core/src/controllers/subscriber.ts +++ b/packages/core/src/controllers/subscriber.ts @@ -32,6 +32,7 @@ import { SubscriberTopicMap } from "./topicmap"; export class Subscriber extends ISubscriber { public subscriptions = new Map(); + public topicMap = new SubscriberTopicMap(); public events = new EventEmitter(); public name = SUBSCRIBER_CONTEXT; @@ -92,6 +93,13 @@ export class Subscriber extends ISubscriber { } get hasAnyTopics() { + console.log( + "hasAnyTopics", + this.topicMap.topics.length, + this.pending.size, + this.cached.length, + this.subscriptions.size, + ); return ( this.topicMap.topics.length > 0 || this.pending.size > 0 || @@ -105,12 +113,17 @@ export class Subscriber extends ISubscriber { this.logger.debug(`Subscribing Topic`); this.logger.trace({ type: "method", method: "subscribe", params: { topic, opts } }); try { + console.log("rpcSubscribe opts", opts); + const relay = getRelayProtocolName(opts); const params = { topic, relay, transportType: opts?.transportType }; - this.pending.set(topic, params); + if (!opts?.internal?.skipSubscribe) { + this.pending.set(topic, params); + } const id = await this.rpcSubscribe(topic, relay, opts); if (typeof id === "string") { this.onSubscribe(id, params); + console.log("onSubscribe", this.hasAnyTopics, globalThis.CoreId); this.logger.debug(`Successfully Subscribed Topic`); this.logger.trace({ type: "method", method: "subscribe", params: { topic, opts } }); } @@ -213,7 +226,7 @@ export class Subscriber extends ISubscriber { private async unsubscribeById(topic: string, id: string, opts?: RelayerTypes.UnsubscribeOptions) { this.logger.debug(`Unsubscribing Topic`); this.logger.trace({ type: "method", method: "unsubscribe", params: { topic, id, opts } }); - + console.log("unsubscribeById", topic, id, opts, globalThis.CoreId); try { const relay = getRelayProtocolName(opts); await this.restartToComplete({ topic, id, relay }); @@ -234,6 +247,16 @@ export class Subscriber extends ISubscriber { relay: RelayerTypes.ProtocolOptions, opts?: RelayerTypes.SubscribeOptions, ) { + const subId = await this.getSubscriptionId(topic); + if (opts?.internal?.skipSubscribe) { + console.log( + "rpcSubscribe opts.internal.skipSubscribe", + opts.internal.skipSubscribe, + topic, + this.hasAnyTopics, + ); + return subId; + } if (!opts || opts?.transportType === TRANSPORT_TYPES.relay) { await this.restartToComplete({ topic, id: topic, relay }); } @@ -248,7 +271,6 @@ export class Subscriber extends ISubscriber { this.logger.trace({ type: "payload", direction: "outgoing", request }); const shouldThrow = opts?.internal?.throwOnFailedPublish; try { - const subId = await this.getSubscriptionId(topic); // in link mode, allow the app to update its network state (i.e. active airplane mode) with small delay before attempting to subscribe if (opts?.transportType === TRANSPORT_TYPES.link_mode) { setTimeout(() => { @@ -457,7 +479,7 @@ export class Subscriber extends ISubscriber { } private restart = async () => { - await this.restore(); + // await this.restore(); await this.onRestart(); }; diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 90beaf381d..6fd88e7780 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -33,6 +33,7 @@ import { Verify, } from "./controllers"; +globalThis.CoreId = Math.random().toString(36).substring(2, 15); export class Core extends ICore { public readonly protocol = CORE_PROTOCOL; public readonly version = CORE_VERSION; @@ -94,8 +95,8 @@ export class Core extends ICore { } } - this.projectId = opts?.projectId; - this.relayUrl = opts?.relayUrl || RELAYER_DEFAULT_RELAY_URL; + this.projectId = "47a263e9e957d954353cb970f024e1d3"; + this.relayUrl = RELAYER_DEFAULT_RELAY_URL; this.customStoragePrefix = opts?.customStoragePrefix ? `:${opts.customStoragePrefix}` : ""; const loggerOptions = getDefaultLoggerOptions({ diff --git a/packages/sign-client/src/controllers/engine.ts b/packages/sign-client/src/controllers/engine.ts index 6d7078cac1..3e1b33ac01 100644 --- a/packages/sign-client/src/controllers/engine.ts +++ b/packages/sign-client/src/controllers/engine.ts @@ -239,7 +239,9 @@ export class Engine extends IEngine { throw error; } if (!topic || !active) { - const { topic: newTopic, uri: newUri } = await this.client.core.pairing.create(); + const { topic: newTopic, uri: newUri } = await this.client.core.pairing.create({ + internal: { skipSubscribe: true }, + }); topic = newTopic; uri = newUri; } @@ -294,13 +296,15 @@ export class Engine extends IEngine { resolve(session); } }); + console.log("sendProposeSession proposal", proposal.pairingTopic); - await this.sendRequest({ - topic, - method: "wc_sessionPropose", - params: proposal, - throwOnFailedPublish: true, - clientRpcId: proposal.id, + await this.sendProposeSession({ + proposal, + publishOpts: { + internal: { + throwOnFailedPublish: true, + }, + }, }); await this.setProposal(proposal.id, proposal); @@ -393,7 +397,10 @@ export class Engine extends IEngine { const transportType = TRANSPORT_TYPES.relay; event.addTrace(EVENT_CLIENT_SESSION_TRACES.subscribing_session_topic); try { - await this.client.core.relayer.subscribe(sessionTopic, { transportType }); + await this.client.core.relayer.subscribe(sessionTopic, { + transportType, + internal: { skipSubscribe: true }, + }); } catch (error) { event.setError(EVENT_CLIENT_SESSION_ERRORS.subscribe_session_topic_failure); throw error; @@ -421,35 +428,60 @@ export class Engine extends IEngine { event.addTrace(EVENT_CLIENT_SESSION_TRACES.store_session); try { - event.addTrace(EVENT_CLIENT_SESSION_TRACES.publishing_session_settle); - await this.sendRequest({ - topic: sessionTopic, - method: "wc_sessionSettle", - params: sessionSettle, - throwOnFailedPublish: true, - }).catch((error) => { - event?.setError(EVENT_CLIENT_SESSION_ERRORS.session_settle_publish_failure); - throw error; - }); - - event.addTrace(EVENT_CLIENT_SESSION_TRACES.session_settle_publish_success); - - event.addTrace(EVENT_CLIENT_SESSION_TRACES.publishing_session_approve); - await this.sendResult<"wc_sessionPropose">({ - id, - topic: pairingTopic, - result: { + // await this.sendRequest({ + // topic: sessionTopic, + // method: "wc_sessionSettle", + // params: sessionSettle, + // throwOnFailedPublish: true, + // }); + + await this.sendApproveSession({ + sessionTopic, + proposal, + pairingProposalResponse: { relay: { protocol: relayProtocol ?? "irn", }, responderPublicKey: selfPublicKey, }, - throwOnFailedPublish: true, - }).catch((error) => { - event?.setError(EVENT_CLIENT_SESSION_ERRORS.session_approve_publish_failure); - throw error; + sessionSettleRequest: sessionSettle, + publishOpts: { + internal: { + throwOnFailedPublish: true, + }, + }, }); + // event.addTrace(EVENT_CLIENT_SESSION_TRACES.publishing_session_settle); + // await this.sendRequest({ + // topic: sessionTopic, + // method: "wc_sessionSettle", + // params: sessionSettle, + // throwOnFailedPublish: true, + // }).catch((error) => { + // event?.setError(EVENT_CLIENT_SESSION_ERRORS.session_settle_publish_failure); + // throw error; + // }); + // console.log("session_settle request published"); + // event.addTrace(EVENT_CLIENT_SESSION_TRACES.session_settle_publish_success); + + // event.addTrace(EVENT_CLIENT_SESSION_TRACES.publishing_session_approve); + // await this.sendResult<"wc_sessionPropose">({ + // id, + // topic: pairingTopic, + // result: { + // relay: { + // protocol: relayProtocol ?? "irn", + // }, + // responderPublicKey: selfPublicKey, + // }, + // throwOnFailedPublish: true, + // }).catch((error) => { + // event?.setError(EVENT_CLIENT_SESSION_ERRORS.session_approve_publish_failure); + // throw error; + // }); + console.log("wc_sessionPropose result published"); + event.addTrace(EVENT_CLIENT_SESSION_TRACES.session_approve_publish_success); } catch (error) { this.client.logger.error(error); @@ -1494,6 +1526,7 @@ export class Engine extends IEngine { throwOnFailedPublish, appLink, tvf, + publishOpts = {}, } = args; const payload = formatJsonRpcRequest(method, params, clientRpcId); @@ -1515,7 +1548,12 @@ export class Engine extends IEngine { const id = hashMessage(message); attestation = await this.client.core.verify.register({ id, decryptedId }); } - const opts = ENGINE_RPC_OPTS[method].req; + + const opts = { + ...ENGINE_RPC_OPTS[method].req, + ...publishOpts, + }; + opts.attestation = attestation; if (expiry) opts.ttl = expiry; if (relayRpcId) opts.id = relayRpcId; @@ -1525,10 +1563,6 @@ export class Engine extends IEngine { const redirectURL = getLinkModeURL(appLink, topic, message); await (global as any).Linking.openURL(redirectURL, this.client.name); } else { - const opts = ENGINE_RPC_OPTS[method].req; - if (expiry) opts.ttl = expiry; - if (relayRpcId) opts.id = relayRpcId; - opts.tvf = { ...tvf, correlationId: payload.id, @@ -1550,6 +1584,116 @@ export class Engine extends IEngine { return payload.id; }; + private sendProposeSession: EnginePrivate["sendProposeSession"] = async (params) => { + const { proposal, publishOpts } = params; + + const proposeSessionPayload = formatJsonRpcRequest("wc_sessionPropose", proposal, proposal.id); + + this.client.core.history.set(proposal.pairingTopic, proposeSessionPayload); + + const proposeSessionMessage = await this.client.core.crypto.encode( + proposal.pairingTopic, + proposeSessionPayload, + { + encoding: BASE64, + }, + ); + + const decryptedId = hashMessage(JSON.stringify(proposeSessionPayload)); + const attestationId = hashMessage(proposeSessionMessage); + const attestation = await this.client.core.verify.register({ id: attestationId, decryptedId }); + + await this.client.core.relayer.publishCustom({ + payload: { + pairingTopic: proposal.pairingTopic, + sessionProposal: proposeSessionMessage, + attestation, + }, + opts: { + ...publishOpts, + id: proposal.id, + publishMethod: "wc_proposeSession", + }, + }); + }; + + private sendApproveSession: EnginePrivate["sendApproveSession"] = async (params) => { + const { sessionTopic, pairingProposalResponse, proposal, sessionSettleRequest, publishOpts } = + params; + const pairingPayload = formatJsonRpcResult(proposal.id, pairingProposalResponse); + + const pairingResponseMessage = await this.client.core.crypto.encode( + proposal.pairingTopic, + pairingPayload, + { + encoding: BASE64, + }, + ); + + const sessionSettlePayload = formatJsonRpcRequest( + "wc_sessionSettle", + sessionSettleRequest, + publishOpts.id, + ); + + this.client.core.history.set(sessionTopic, sessionSettlePayload); + + const sessionSettlementRequestMessage = await this.client.core.crypto.encode( + sessionTopic, + sessionSettlePayload, + { + encoding: BASE64, + }, + ); + + this.client.core.history.set(sessionTopic, sessionSettlePayload); + + await this.client.core.relayer.publishCustom({ + payload: { + sessionTopic, + pairingTopic: proposal.pairingTopic, + pairingResponse: pairingResponseMessage, + sessionSettlementRequest: sessionSettlementRequestMessage, + }, + opts: { + ...publishOpts, + publishMethod: "wc_approveSession", + }, + }); + }; + + // private sendBatchRequest: EnginePrivate["sendBatchRequest"] = async (args) => { + // const { sharedPayload, requests, throwOnFailedPublish, tvf, publishOpts = {} } = args; + + // const messages = {}; + // for (const [key, request] of Object.entries(requests)) { + // const { topic, method, params, expiry, relayRpcId, clientRpcId } = request; + // const payload = formatJsonRpcRequest(method, params, clientRpcId); + + // let message: string; + + // try { + // message = await this.client.core.crypto.encode(topic, payload, { + // encoding: BASE64, + // }); + // } catch (error) { + // await this.cleanup(); + // this.client.logger.error( + // `sendBatchRequest() -> core.crypto.encode() for topic ${topic} failed`, + // ); + // throw error; + // } + + // this.client.core.history.set(topic, payload); + + // messages[key] = message; + // } + + // await this.client.core.relayer.publishCustom({}); + + // return payload.id; + // }; + private sendResult: EnginePrivate["sendResult"] = async (args) => { const { id, topic, result, throwOnFailedPublish, encodeOpts, appLink } = args; const payload = formatJsonRpcResult(id, result); @@ -1905,6 +2049,7 @@ export class Engine extends IEngine { transportType, ) => { const { id } = payload; + console.log("wc_sessionPropose response received", payload); if (isJsonRpcResult(payload)) { const { result } = payload; this.client.logger.trace({ type: "method", method: "onSessionProposeResponse", result }); diff --git a/packages/sign-client/test/sdk/client.spec.ts b/packages/sign-client/test/sdk/client.spec.ts index 6c87aee5d5..6dbf482a18 100644 --- a/packages/sign-client/test/sdk/client.spec.ts +++ b/packages/sign-client/test/sdk/client.spec.ts @@ -75,11 +75,9 @@ describe("Sign Client Integration", () => { describe("connect", () => { it("connect (with new pairing)", async () => { - const { clients, sessionA, pairingA } = await initTwoPairedClients( - {}, - {}, - { logger: "error" }, - ); + const clients = await initTwoClients({}, {}, { logger: "warn" }); + const { pairingA, sessionA } = await testConnectMethod(clients); + expect(pairingA).to.be.exist; expect(sessionA).to.be.exist; expect(pairingA.topic).to.eq(sessionA.pairingTopic); diff --git a/packages/types/src/core/pairing.ts b/packages/types/src/core/pairing.ts index df2de7f1e0..b8561463e9 100644 --- a/packages/types/src/core/pairing.ts +++ b/packages/types/src/core/pairing.ts @@ -79,6 +79,7 @@ export abstract class IPairing { public abstract create(params?: { methods?: string[]; transportType?: RelayerTypes.SubscribeOptions["transportType"]; + internal?: RelayerTypes.SubscribeOptions["internal"]; }): Promise<{ topic: string; uri: string }>; // for either to activate a previously created pairing diff --git a/packages/types/src/core/publisher.ts b/packages/types/src/core/publisher.ts index c7d8e2d628..60dfbfde96 100644 --- a/packages/types/src/core/publisher.ts +++ b/packages/types/src/core/publisher.ts @@ -28,4 +28,9 @@ export abstract class IPublisher extends IEvents { message: string, opts?: RelayerTypes.PublishOptions, ): Promise; + + public abstract publishCustom(params: { + payload: any; + opts?: RelayerTypes.PublishOptions; + }): Promise; } diff --git a/packages/types/src/core/relayer.ts b/packages/types/src/core/relayer.ts index 5485acda53..e65aff7ccf 100644 --- a/packages/types/src/core/relayer.ts +++ b/packages/types/src/core/relayer.ts @@ -23,6 +23,7 @@ export declare namespace RelayerTypes { }; tvf?: ITVF; attestation?: string; + publishMethod?: string; } export type TransportType = "relay" | "link_mode"; @@ -32,6 +33,7 @@ export declare namespace RelayerTypes { transportType?: TransportType; internal?: { throwOnFailedPublish?: boolean; + skipSubscribe?: boolean; }; } @@ -134,6 +136,11 @@ export abstract class IRelayer extends IEvents { opts?: RelayerTypes.PublishOptions, ): Promise; + public abstract publishCustom(params: { + payload: any; + opts?: RelayerTypes.PublishOptions; + }): Promise; + public abstract request(request: RequestArguments): Promise; public abstract subscribe(topic: string, opts?: RelayerTypes.SubscribeOptions): Promise; diff --git a/packages/types/src/sign-client/engine.ts b/packages/types/src/sign-client/engine.ts index 4ccc012245..097c61cf4f 100644 --- a/packages/types/src/sign-client/engine.ts +++ b/packages/types/src/sign-client/engine.ts @@ -189,6 +189,23 @@ export interface EnginePrivate { throwOnFailedPublish?: boolean; appLink?: string; tvf?: RelayerTypes.ITVF; + publishOpts?: RelayerTypes.PublishOptions; + }): Promise; + + sendBatchRequest(args: { + sharedPayload: Record; + requests: Record; + throwOnFailedPublish?: boolean; + appLink?: string; + tvf?: RelayerTypes.ITVF; + publishOpts?: RelayerTypes.PublishOptions; }): Promise; sendResult(args: { @@ -198,6 +215,7 @@ export interface EnginePrivate { throwOnFailedPublish?: boolean; encodeOpts?: CryptoTypes.EncodeOptions; appLink?: string; + publishOpts?: RelayerTypes.PublishOptions; }): Promise; sendError(params: { @@ -207,6 +225,20 @@ export interface EnginePrivate { encodeOpts?: CryptoTypes.EncodeOptions; rpcOpts?: RelayerTypes.PublishOptions; appLink?: string; + publishOpts?: RelayerTypes.PublishOptions; + }): Promise; + + sendApproveSession(params: { + sessionTopic: string; + proposal: ProposalTypes.Struct; + pairingProposalResponse: JsonRpcTypes.Results[JsonRpcTypes.WcMethod]; + sessionSettleRequest: JsonRpcTypes.RequestParams[JsonRpcTypes.WcMethod]; + publishOpts: RelayerTypes.PublishOptions; + }): Promise; + + sendProposeSession(params: { + proposal: ProposalTypes.Struct; + publishOpts: RelayerTypes.PublishOptions; }): Promise; onRelayEventRequest(event: EngineTypes.EventCallback): Promise; From a055b655601103616e2561950ffd102d14e6dc64 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 23 May 2025 11:29:45 +0300 Subject: [PATCH 2/3] chore: prettier --- packages/core/src/controllers/subscriber.ts | 1 + packages/types/src/sign-client/engine.ts | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/core/src/controllers/subscriber.ts b/packages/core/src/controllers/subscriber.ts index 8c1eec55e0..29bcaf9970 100644 --- a/packages/core/src/controllers/subscriber.ts +++ b/packages/core/src/controllers/subscriber.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import { EventEmitter } from "events"; import { HEARTBEAT_EVENTS } from "@walletconnect/heartbeat"; import { ErrorResponse, RequestArguments } from "@walletconnect/jsonrpc-types"; diff --git a/packages/types/src/sign-client/engine.ts b/packages/types/src/sign-client/engine.ts index 097c61cf4f..db86d7bf32 100644 --- a/packages/types/src/sign-client/engine.ts +++ b/packages/types/src/sign-client/engine.ts @@ -194,14 +194,17 @@ export interface EnginePrivate { sendBatchRequest(args: { sharedPayload: Record; - requests: Record; + requests: Record< + string, + { + topic: string; + method: M; + params: JsonRpcTypes.RequestParams[M]; + expiry?: number; + relayRpcId?: number; + clientRpcId?: number; + } + >; throwOnFailedPublish?: boolean; appLink?: string; tvf?: RelayerTypes.ITVF; From a218e9816dbe135f59ef317e32aedc166a62e39f Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 23 May 2025 11:39:14 +0300 Subject: [PATCH 3/3] fix: publisher tests queue payload structure --- packages/core/test/publisher.spec.ts | 111 ++++++++++++++++++++++----- 1 file changed, 90 insertions(+), 21 deletions(-) diff --git a/packages/core/test/publisher.spec.ts b/packages/core/test/publisher.spec.ts index 2984c6e554..e4097fa904 100644 --- a/packages/core/test/publisher.spec.ts +++ b/packages/core/test/publisher.spec.ts @@ -32,29 +32,59 @@ describe("Publisher", () => { const opts = { ttl: 1, prompt: true, relay: { protocol: "irn" }, tag: 0 }; const items = [ { - topic: generateRandomBytes32(), - message: "itemA", + request: { + method: "irn_publish", + params: { + topic: generateRandomBytes32(), + message: "itemA", + }, + }, opts: { ...opts, id: getId() }, + attempt: 1, }, { - topic: generateRandomBytes32(), - message: "itemB", + request: { + method: "irn_publish", + params: { + topic: generateRandomBytes32(), + message: "itemB", + }, + }, opts: { ...opts, id: getId() }, + attempt: 1, }, { - topic: generateRandomBytes32(), - message: "itemC", + request: { + method: "irn_publish", + params: { + topic: generateRandomBytes32(), + message: "itemC", + }, + }, opts: { ...opts, id: getId() }, + attempt: 1, }, { - topic: generateRandomBytes32(), - message: "itemD", + request: { + method: "irn_publish", + params: { + topic: generateRandomBytes32(), + message: "itemD", + }, + }, opts: { ...opts, id: getId() }, + attempt: 1, }, { - topic: generateRandomBytes32(), - message: "itemE", + request: { + method: "irn_publish", + params: { + topic: generateRandomBytes32(), + message: "itemE", + }, + }, opts: { ...opts, id: getId() }, + attempt: 1, }, ]; @@ -62,7 +92,16 @@ describe("Publisher", () => { publisher.relayer.request = requestSpy; // Manually set some items in the queue. - items.forEach((item) => publisher.queue.set(item.opts.id.toString(), item)); + items.forEach((item) => + publisher.queue.set(item.opts.id.toString(), { + request: { + method: "irn_publish", + params: item.message, + }, + opts: item.opts, + attempt: 1, + }), + ); expect(publisher.queue.size).to.equal(items.length); // Emit heartbeat pulse event publisher.relayer.core.heartbeat.events.emit(HEARTBEAT_EVENTS.pulse); @@ -89,29 +128,59 @@ describe("Publisher", () => { const opts = { ttl: 1, prompt: true, relay: { protocol: "irn" }, tag: 0 }; const items = [ { - topic: generateRandomBytes32(), - message: "itemA", + request: { + method: "irn_publish", + params: { + topic: generateRandomBytes32(), + message: "itemA", + }, + }, opts: { ...opts, id: getId() }, + attempt: 1, }, { - topic: generateRandomBytes32(), - message: "itemB", + request: { + method: "irn_publish", + params: { + topic: generateRandomBytes32(), + message: "itemB", + }, + }, opts: { ...opts, id: getId() }, + attempt: 1, }, { - topic: generateRandomBytes32(), - message: "itemC", + request: { + method: "irn_publish", + params: { + topic: generateRandomBytes32(), + message: "itemC", + }, + }, opts: { ...opts, id: getId() }, + attempt: 1, }, { - topic: generateRandomBytes32(), - message: "itemD", + request: { + method: "irn_publish", + params: { + topic: generateRandomBytes32(), + message: "itemD", + }, + }, opts: { ...opts, id: getId() }, + attempt: 1, }, { - topic: generateRandomBytes32(), - message: "itemE", + request: { + method: "irn_publish", + params: { + topic: generateRandomBytes32(), + message: "itemE", + }, + }, opts: { ...opts, id: getId() }, + attempt: 1, }, ];