Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: tvf #5630

Merged
merged 5 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions packages/core/src/controllers/publisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ 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 } from "@walletconnect/types";
import { IPublisher, IRelayer, PublisherTypes, RelayerTypes } from "@walletconnect/types";
import {
getRelayProtocolApi,
getRelayProtocolName,
Expand Down Expand Up @@ -58,6 +58,7 @@ export class Publisher extends IPublisher {
tag,
id,
attestation: opts?.attestation,
tvf: opts?.tvf,
},
};
const failedPublishMessage = `Failed to publish payload, please try again. id:${id} tag:${tag}`;
Expand Down Expand Up @@ -87,6 +88,7 @@ export class Publisher extends IPublisher {
tag,
id,
attestation: opts?.attestation,
tvf: opts?.tvf,
})
.then(resolve)
.catch((e) => {
Expand Down Expand Up @@ -149,8 +151,18 @@ export class Publisher extends IPublisher {
tag?: number;
id?: number;
attestation?: string;
tvf?: RelayerTypes.ITVF;
}) {
const { topic, message, ttl = PUBLISHER_DEFAULT_TTL, prompt, tag, id, attestation } = params;
const {
topic,
message,
ttl = PUBLISHER_DEFAULT_TTL,
prompt,
tag,
id,
attestation,
tvf,
} = params;
const api = getRelayProtocolApi(getRelayProtocolName().protocol);
const request: RequestArguments<RelayJsonRpc.PublishParams> = {
method: api.publish,
Expand All @@ -161,6 +173,7 @@ export class Publisher extends IPublisher {
prompt,
tag,
attestation,
...tvf,
},
id,
};
Expand Down Expand Up @@ -188,13 +201,15 @@ export class Publisher extends IPublisher {
`Publisher: queue->publishing: ${params.opts.id}, tag: ${params.opts.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}`);
});
Expand Down
22 changes: 22 additions & 0 deletions packages/sign-client/src/constants/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,25 @@ export const ENGINE_QUEUE_STATES: { idle: "IDLE"; active: "ACTIVE" } = {
idle: "IDLE",
active: "ACTIVE",
};

export const TVF_METHODS = {
eth_sendTransaction: {
key: "",
},
eth_sendRawTransaction: {
key: "",
},
wallet_sendCalls: {
key: "",
},

solana_signTransaction: {
key: "signature",
},
solana_signAllTransactions: {
key: "transactions",
},
solana_signAndSendTransaction: {
key: "signature",
},
};
125 changes: 115 additions & 10 deletions packages/sign-client/src/controllers/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
getSearchParamFromURL,
isReactNative,
isTestRun,
isValidArray,
} from "@walletconnect/utils";
import EventEmmiter from "events";
import {
Expand All @@ -109,6 +110,7 @@
WALLETCONNECT_DEEPLINK_CHOICE,
ENGINE_QUEUE_STATES,
AUTH_PUBLIC_KEY_NAME,
TVF_METHODS,
} from "../constants";

export class Engine extends IEngine {
Expand Down Expand Up @@ -557,14 +559,14 @@
else resolve(result);
},
);

const protocolMethod = "wc_sessionRequest";
const appLink = this.getAppLinkIfEnabled(session.peer.metadata, session.transportType);
if (appLink) {
await this.sendRequest({
clientRpcId,
relayRpcId,
topic,
method: "wc_sessionRequest",
method: protocolMethod,
params: {
request: {
...request,
Expand All @@ -587,22 +589,28 @@
return result;
}

const protocolRequestParams: JsonRpcTypes.RequestParams["wc_sessionRequest"] = {
request: {
...request,
expiryTimestamp: calcExpiry(expiry),
},
chainId,
};
const shouldSetTVF = this.shouldSetTVF(protocolMethod, protocolRequestParams);

return await Promise.all([
new Promise<void>(async (resolve) => {
await this.sendRequest({
clientRpcId,
relayRpcId,
topic,
method: "wc_sessionRequest",
params: {
request: {
...request,
expiryTimestamp: calcExpiry(expiry),
},
chainId,
},
method: protocolMethod,
params: protocolRequestParams,
expiry,
throwOnFailedPublish: true,
...(shouldSetTVF && {
tvf: this.getTVFParams(clientRpcId, protocolRequestParams),
}),
}).catch((error) => reject(error));
this.client.events.emit("session_request_sent", {
topic,
Expand Down Expand Up @@ -1449,6 +1457,7 @@
clientRpcId,
throwOnFailedPublish,
appLink,
tvf,
} = args;
const payload = formatJsonRpcRequest(method, params, clientRpcId);

Expand Down Expand Up @@ -1483,6 +1492,12 @@
const opts = ENGINE_RPC_OPTS[method].req;
if (expiry) opts.ttl = expiry;
if (relayRpcId) opts.id = relayRpcId;

opts.tvf = {
...tvf,
correlationId: payload.id,
};

if (throwOnFailedPublish) {
opts.internal = {
...opts.internal,
Expand Down Expand Up @@ -1518,8 +1533,17 @@
throw error;
}
let record;
let tvf;
try {
record = await this.client.core.history.get(topic, id);
const request = record.request;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we validate/parse the request to avoid having to cast the method to JsonRpcTypes.WcMethod?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request.methodtype is string so i'm casting it to predefined set of protocol methods to be more intuitive of the values. I can try to refactor the type JsonRpcRecord that history returns but it might cause unexpected breaking changes in the types

try {
if (this.shouldSetTVF(request.method as JsonRpcTypes.WcMethod, request.params)) {
tvf = this.getTVFParams(id, request.params, result);
}
} catch (error) {
this.client.logger.warn(`sendResult() -> getTVFParams() failed`, error);
}
} catch (error) {
this.client.logger.error(`sendResult() -> history.get(${topic}, ${id}) failed`);
throw error;
Expand All @@ -1530,6 +1554,12 @@
await (global as any).Linking.openURL(redirectURL, this.client.name);
} else {
const opts = ENGINE_RPC_OPTS[record.request.method].res;

opts.tvf = {
...tvf,
correlationId: id,
};

if (throwOnFailedPublish) {
opts.internal = {
...opts.internal,
Expand Down Expand Up @@ -2084,7 +2114,7 @@
}, 500);
};

private onSessionDeleteRequest: EnginePrivate["onSessionDeleteRequest"] = async (

Check warning on line 2117 in packages/sign-client/src/controllers/engine.ts

View workflow job for this annotation

GitHub Actions / code_style (lint)

Async method 'onSessionDeleteRequest' has no 'await' expression
topic,
payload,
) => {
Expand Down Expand Up @@ -2941,4 +2971,79 @@
}
}
};

private shouldSetTVF = (
protocolMethod: JsonRpcTypes.WcMethod,
params: JsonRpcTypes.RequestParams["wc_sessionRequest"],
) => {
if (!params) return false;
if (protocolMethod !== "wc_sessionRequest") return false;
const { request } = params;
return Object.keys(TVF_METHODS).includes(request.method);
};

private getTVFParams = (
id: number,
params: JsonRpcTypes.RequestParams["wc_sessionRequest"],
result?: any,
) => {
try {
const requestMethod = params.request.method;
const txHashes = this.extractTxHashesFromResult(requestMethod, result);
const tvf: RelayerTypes.ITVF = {
correlationId: id,
ganchoradkov marked this conversation as resolved.
Show resolved Hide resolved
rpcMethods: [requestMethod],
chainId: params.chainId,
...(this.isValidContractData(params.request.params) && {
// initially only get contractAddresses from EVM txs
contractAddresses: [params.request.params?.[0]?.to],
}),
txHashes,
};
return tvf;
} catch (e) {
this.client.logger.warn("Error getting TVF params", e);
}
return {};
Comment on lines +3004 to +3007
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious why you're adopting this pattern of returning outside of the catch ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems easier to read whats going on in the fx

};

private isValidContractData = (params: any) => {
if (!params) return false;
try {
const data = params?.data || params?.[0]?.data;

if (!data.startsWith("0x")) return false;

const hexPart = data.slice(2);
if (!/^[0-9a-fA-F]*$/.test(hexPart)) return false;

return hexPart.length % 2 === 0;
} catch (e) {}
return false;
};

private extractTxHashesFromResult = (method: string, result: any): string[] => {
try {
const methodConfig = TVF_METHODS[method as keyof typeof TVF_METHODS];
// result = 0x...
if (typeof result === "string") {
return [result];
}

// result = { key: [0x...] } or { key: 0x... }
const hashes = result[methodConfig.key];

// result = { key: [0x...] }
if (isValidArray(hashes)) {
return hashes;

// result = { key: 0x... }
} else if (typeof hashes === "string") {
return [hashes];
}
} catch (e) {
this.client.logger.warn("Error extracting tx hashes from result", e);
}
return [];
};
}
Loading
Loading