Skip to content

Commit f6521cf

Browse files
✨ (ledger-button) [NO-ISSUE]: Add deep link tracking params for analytics (#285)
2 parents 65c6dc1 + 86dab2f commit f6521cf

File tree

3 files changed

+121
-23
lines changed

3 files changed

+121
-23
lines changed

packages/ledger-button/src/domain/home/ledger-home.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,13 @@ export class LedgerHomeScreen extends LitElement {
161161

162162
void this.coreContext.trackWalletRedirectConfirmed(action);
163163

164-
const deeplink = buildWalletActionDeepLink(action, {
165-
currency: this.controller.selectedAccount?.currencyId,
166-
});
164+
const deeplink = buildWalletActionDeepLink(
165+
action,
166+
{
167+
currency: this.controller.selectedAccount?.currencyId,
168+
},
169+
this.coreContext.getConfig().dAppIdentifier,
170+
);
167171
window.open(deeplink, "_blank", "noopener,noreferrer");
168172

169173
this.showRedirectDrawer = false;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { describe, expect, test } from "vitest";
2+
3+
import { buildWalletActionDeepLink } from "./deeplinks.js";
4+
5+
describe("buildWalletActionDeepLink", () => {
6+
describe("without partner", () => {
7+
test("returns base URL when no context", () => {
8+
expect(buildWalletActionDeepLink("swap")).toBe("ledgerwallet://swap");
9+
expect(buildWalletActionDeepLink("send")).toBe("ledgerwallet://send");
10+
expect(buildWalletActionDeepLink("buy")).toBe("ledgerwallet://buy");
11+
});
12+
13+
test("appends currency params when context has currency", () => {
14+
expect(buildWalletActionDeepLink("send", { currency: "eth" })).toBe(
15+
"ledgerwallet://send?currency=eth",
16+
);
17+
expect(buildWalletActionDeepLink("swap", { currency: "usdc" })).toBe(
18+
"ledgerwallet://swap?fromToken=usdc",
19+
);
20+
expect(buildWalletActionDeepLink("earn", { currency: "btc" })).toBe(
21+
"ledgerwallet://earn?cryptoAssetId=btc",
22+
);
23+
});
24+
});
25+
26+
describe("with partner", () => {
27+
test("appends tracking query params with fixed deeplinkType and deeplinkChannel", () => {
28+
const url = buildWalletActionDeepLink("swap", undefined, "MyPartner");
29+
expect(url).toContain("deeplinkType=Internal");
30+
expect(url).toContain("deeplinkChannel=Button");
31+
expect(url).toContain("deeplinkDestination=swap");
32+
expect(url).toContain("deeplinkButtonPartner=MyPartner");
33+
});
34+
35+
test("maps each action to correct deeplinkDestination (same as route)", () => {
36+
const partner = "Partner";
37+
expect(buildWalletActionDeepLink("send", undefined, partner)).toContain(
38+
"deeplinkDestination=send",
39+
);
40+
expect(buildWalletActionDeepLink("receive", undefined, partner)).toContain(
41+
"deeplinkDestination=receive",
42+
);
43+
expect(buildWalletActionDeepLink("swap", undefined, partner)).toContain(
44+
"deeplinkDestination=swap",
45+
);
46+
expect(buildWalletActionDeepLink("buy", undefined, partner)).toContain(
47+
"deeplinkDestination=buy",
48+
);
49+
expect(buildWalletActionDeepLink("earn", undefined, partner)).toContain(
50+
"deeplinkDestination=earn",
51+
);
52+
expect(buildWalletActionDeepLink("sell", undefined, partner)).toContain(
53+
"deeplinkDestination=buy",
54+
);
55+
});
56+
57+
test("preserves existing currency params when partner is provided", () => {
58+
const url = buildWalletActionDeepLink(
59+
"swap",
60+
{ currency: "eth" },
61+
"MyPartner",
62+
);
63+
expect(url).toContain("fromToken=eth");
64+
expect(url).toContain("deeplinkType=Internal");
65+
expect(url).toContain("deeplinkButtonPartner=MyPartner");
66+
});
67+
68+
test("URL-encodes partner with special characters", () => {
69+
const url = buildWalletActionDeepLink("buy", undefined, "Partner & Co");
70+
expect(url).toContain("deeplinkButtonPartner=Partner+%26+Co");
71+
});
72+
});
73+
});

packages/ledger-button/src/shared/constants/deeplinks.ts

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,18 @@ export type DeepLinkContext = {
88
address?: string;
99
};
1010

11+
const BASE_URL = "ledgerwallet://";
1112
/**
1213
* Base deep links for wallet actions.
1314
* Note: "sell" is not supported in Ledger Live Desktop, falls back to "buy".
1415
*/
1516
const BASE_DEEPLINKS: Record<WalletTransactionFeature, string> = {
16-
send: "ledgerwallet://send",
17-
receive: "ledgerwallet://receive",
18-
swap: "ledgerwallet://swap",
19-
buy: "ledgerwallet://buy",
20-
earn: "ledgerwallet://earn",
21-
sell: "ledgerwallet://buy",
17+
send: "send",
18+
receive: "receive",
19+
swap: "swap",
20+
buy: "buy",
21+
earn: "earn",
22+
sell: "buy",
2223
};
2324

2425
/**
@@ -31,30 +32,50 @@ const BASE_DEEPLINKS: Record<WalletTransactionFeature, string> = {
3132
* - buy: no params (params passed through to liveApp)
3233
* - earn: ?cryptoAssetId={currency} (pre-fills asset for deposit)
3334
* - sell: not supported in Desktop, falls back to buy
35+
*
36+
* When partner is provided, appends tracking query params: deeplinkType, deeplinkDestination,
37+
* deeplinkChannel, deeplinkButtonPartner (for deeplink_clicked analytics).
3438
*/
3539
export function buildWalletActionDeepLink(
3640
action: WalletTransactionFeature,
3741
context?: DeepLinkContext,
42+
partner?: string,
3843
): string {
39-
const baseUrl = BASE_DEEPLINKS[action];
44+
const route = BASE_DEEPLINKS[action];
45+
const baseUrl = `${BASE_URL}${route}`;
4046

47+
let urlString: string;
4148
if (!context?.currency) {
42-
return baseUrl;
49+
urlString = baseUrl;
50+
} else {
51+
switch (action) {
52+
case "send":
53+
case "receive":
54+
urlString = `${baseUrl}?currency=${context.currency}`;
55+
break;
56+
case "swap":
57+
urlString = `${baseUrl}?fromToken=${context.currency}`;
58+
break;
59+
case "earn":
60+
urlString = `${baseUrl}?cryptoAssetId=${context.currency}`;
61+
break;
62+
case "buy":
63+
case "sell":
64+
default:
65+
urlString = baseUrl;
66+
}
4367
}
4468

45-
switch (action) {
46-
case "send":
47-
case "receive":
48-
return `${baseUrl}?currency=${context.currency}`;
49-
case "swap":
50-
return `${baseUrl}?fromToken=${context.currency}`;
51-
case "earn":
52-
return `${baseUrl}?cryptoAssetId=${context.currency}`;
53-
case "buy":
54-
case "sell":
55-
default:
56-
return baseUrl;
69+
if (!partner) {
70+
return urlString;
5771
}
72+
73+
const url = new URL(urlString);
74+
url.searchParams.set("deeplinkType", "Internal");
75+
url.searchParams.set("deeplinkDestination", route);
76+
url.searchParams.set("deeplinkChannel", "Button");
77+
url.searchParams.set("deeplinkButtonPartner", partner);
78+
return url.toString();
5879
}
5980

6081
/**

0 commit comments

Comments
 (0)