Skip to content

Commit 5f677f0

Browse files
authored
Merge branch 'main' into sol-sdk-upgrades
2 parents 3fa3e22 + 924730a commit 5f677f0

File tree

3 files changed

+196
-12
lines changed

3 files changed

+196
-12
lines changed

cli/src/commands/token-transfer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ function buildExecutorRouteConfig(
793793
};
794794
}
795795

796-
type ReferrerFeeConfig = NonNullable<NttExecutorRoute.Config["referrerFee"]>;
796+
type ReferrerFeeConfig = NttExecutorRoute.ReferrerFeeConfig;
797797
type PerTokenOverrides = NonNullable<
798798
NonNullable<ReferrerFeeConfig["perTokenOverrides"]>[Chain]
799799
>;
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import {
2+
Wormhole,
3+
routes,
4+
type Network,
5+
} from "@wormhole-foundation/sdk-connect";
6+
7+
import "@wormhole-foundation/sdk-definitions-ntt";
8+
import "@wormhole-foundation/sdk-evm-ntt";
9+
import "@wormhole-foundation/sdk-solana-ntt";
10+
11+
import { EvmPlatform } from "@wormhole-foundation/sdk-evm";
12+
import { SolanaPlatform } from "@wormhole-foundation/sdk-solana";
13+
import {
14+
nttExecutorRoute,
15+
NttExecutorRoute,
16+
} from "../src/executor/executor.js";
17+
18+
const SOL_TOKEN = "EetppHswYvV1jjRWoQKC1hejdeBDHR9NNzNtCyRQfrrQ";
19+
const SEPOLIA_TOKEN = "0x738141EFf659625F2eAD4feECDfCD94155C67f18";
20+
21+
const nttConfig: NttExecutorRoute.Config["ntt"] = {
22+
tokens: {
23+
TestToken: [
24+
{
25+
chain: "Solana",
26+
token: SOL_TOKEN,
27+
manager: "NTtAaoDJhkeHeaVUHnyhwbPNAN6WgBpHkHBTc6d7vLK",
28+
transceiver: [
29+
{
30+
type: "wormhole",
31+
address: "ExVbjD8inGXkt7Cx8jVr4GF175sQy1MeqgfaY53Ah8as",
32+
},
33+
],
34+
quoter: "Nqd6XqA8LbsCuG8MLWWuP865NV6jR1MbXeKxD4HLKDJ",
35+
},
36+
{
37+
chain: "Sepolia",
38+
token: SEPOLIA_TOKEN,
39+
manager: "0x649fF7B32C2DE771043ea105c4aAb2D724497238",
40+
transceiver: [
41+
{
42+
type: "wormhole",
43+
address: "0x06413c42e913327Bc9a08B7C1E362BAE7C0b9598",
44+
},
45+
],
46+
},
47+
],
48+
},
49+
};
50+
51+
const wh = new Wormhole("Testnet", [SolanaPlatform, EvmPlatform]);
52+
53+
function createRoute(config: NttExecutorRoute.Config) {
54+
const Route = nttExecutorRoute(config);
55+
return new Route(wh);
56+
}
57+
58+
describe("NttExecutorRoute referrer fee", () => {
59+
let request: routes.RouteTransferRequest<"Testnet">;
60+
61+
beforeAll(async () => {
62+
request = await routes.RouteTransferRequest.create(wh, {
63+
source: Wormhole.tokenId("Solana", SOL_TOKEN),
64+
destination: Wormhole.tokenId("Sepolia", SEPOLIA_TOKEN),
65+
});
66+
});
67+
68+
it("uses getReferrerFee callback when defined", async () => {
69+
const getReferrerFee = jest.fn(async () => ({
70+
feeDbps: 42n,
71+
referrerAddress: "0x9b2A3B92b1D86938D3Ed37B0519952C227bA6D09",
72+
}));
73+
const route = createRoute({ ntt: nttConfig, getReferrerFee });
74+
75+
const result = await route.validate(request, { amount: "1.0" });
76+
expect(result.valid).toBe(true);
77+
if (!result.valid) throw new Error("unexpected");
78+
79+
expect(getReferrerFee).toHaveBeenCalledWith({
80+
sourceChain: "Solana",
81+
sourceToken: SOL_TOKEN,
82+
destinationChain: "Sepolia",
83+
destinationToken: SEPOLIA_TOKEN,
84+
});
85+
const params = result.params as NttExecutorRoute.ValidatedParams;
86+
expect(params.normalizedParams.referrerFeeDbps).toBe(42n);
87+
});
88+
89+
it("uses static referrerFee when getReferrerFee is not defined", async () => {
90+
const route = createRoute({
91+
ntt: nttConfig,
92+
referrerFee: { feeDbps: 100n },
93+
});
94+
95+
const result = await route.validate(request, { amount: "1.0" });
96+
expect(result.valid).toBe(true);
97+
if (!result.valid) throw new Error("unexpected");
98+
99+
const params = result.params as NttExecutorRoute.ValidatedParams;
100+
expect(params.normalizedParams.referrerFeeDbps).toBe(100n);
101+
});
102+
103+
it("getReferrerFee takes priority over static referrerFee", async () => {
104+
const route = createRoute({
105+
ntt: nttConfig,
106+
referrerFee: { feeDbps: 100n },
107+
getReferrerFee: async () => ({
108+
feeDbps: 77n,
109+
referrerAddress: "0x9b2A3B92b1D86938D3Ed37B0519952C227bA6D09",
110+
}),
111+
});
112+
113+
const result = await route.validate(request, { amount: "1.0" });
114+
expect(result.valid).toBe(true);
115+
if (!result.valid) throw new Error("unexpected");
116+
117+
const params = result.params as NttExecutorRoute.ValidatedParams;
118+
expect(params.normalizedParams.referrerFeeDbps).toBe(77n);
119+
});
120+
121+
it("defaults to 0 when neither is defined", async () => {
122+
const route = createRoute({ ntt: nttConfig });
123+
124+
const result = await route.validate(request, { amount: "1.0" });
125+
expect(result.valid).toBe(true);
126+
if (!result.valid) throw new Error("unexpected");
127+
128+
const params = result.params as NttExecutorRoute.ValidatedParams;
129+
expect(params.normalizedParams.referrerFeeDbps).toBe(0n);
130+
});
131+
132+
it("resolves per-token feeDbps override from static config", async () => {
133+
const route = createRoute({
134+
ntt: nttConfig,
135+
referrerFee: {
136+
feeDbps: 10n,
137+
perTokenOverrides: {
138+
Solana: {
139+
[SOL_TOKEN]: { referrerFeeDbps: 50n },
140+
},
141+
},
142+
},
143+
});
144+
145+
const result = await route.validate(request, { amount: "1.0" });
146+
expect(result.valid).toBe(true);
147+
if (!result.valid) throw new Error("unexpected");
148+
149+
const params = result.params as NttExecutorRoute.ValidatedParams;
150+
expect(params.normalizedParams.referrerFeeDbps).toBe(50n);
151+
});
152+
});

sdk/route/src/executor/executor.ts

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ import { getDefaultReferrerAddress } from "./consts.js";
5757
export namespace NttExecutorRoute {
5858
export type Config = {
5959
ntt: NttRoute.Config;
60+
/** @deprecated Use getReferrerFee instead */
6061
referrerFee?: ReferrerFeeConfig;
62+
// Takes priority over referrerFee if defined
63+
getReferrerFee?: ReferrerFeeCallback;
6164
};
6265

6366
export type ReferrerFeeConfig = {
@@ -82,6 +85,18 @@ export namespace NttExecutorRoute {
8285
>;
8386
};
8487

88+
export type ReferrerFeeResult = {
89+
feeDbps: bigint;
90+
referrerAddress: string;
91+
};
92+
93+
export type ReferrerFeeCallback = (token: {
94+
sourceChain: Chain;
95+
sourceToken: string;
96+
destinationChain: Chain;
97+
destinationToken: string;
98+
}) => Promise<ReferrerFeeResult>;
99+
85100
export type Options = {
86101
// 0.0 - 1.0 percentage of the maximum gas drop-off amount
87102
nativeGas?: number;
@@ -92,6 +107,7 @@ export namespace NttExecutorRoute {
92107
sourceContracts: Ntt.Contracts;
93108
destinationContracts: Ntt.Contracts;
94109
referrerFeeDbps: bigint;
110+
referrerAddress?: ChainAddress;
95111
};
96112

97113
export interface ValidatedParams
@@ -212,7 +228,20 @@ export class NttExecutorRoute<N extends Network>
212228
);
213229

214230
let referrerFeeDbps = 0n;
215-
if (this.staticConfig.referrerFee) {
231+
let referrerAddress = getDefaultReferrerAddress(request.source.id.chain);
232+
if (this.staticConfig.getReferrerFee) {
233+
const result = await this.staticConfig.getReferrerFee({
234+
sourceChain: request.source.id.chain,
235+
sourceToken: canonicalAddress(request.source.id),
236+
destinationChain: request.destination.id.chain,
237+
destinationToken: canonicalAddress(request.destination.id),
238+
});
239+
referrerFeeDbps = result.feeDbps;
240+
referrerAddress = Wormhole.chainAddress(
241+
request.source.id.chain,
242+
result.referrerAddress
243+
);
244+
} else if (this.staticConfig.referrerFee) {
216245
referrerFeeDbps = this.staticConfig.referrerFee.feeDbps;
217246
if (this.staticConfig.referrerFee.perTokenOverrides) {
218247
const srcTokenAddress = canonicalAddress(request.source.id);
@@ -224,6 +253,15 @@ export class NttExecutorRoute<N extends Network>
224253
referrerFeeDbps = override.referrerFeeDbps;
225254
}
226255
}
256+
const platform = chainToPlatform(request.source.id.chain);
257+
const configuredAddress =
258+
this.staticConfig.referrerFee.referrerAddresses?.[platform];
259+
if (configuredAddress) {
260+
referrerAddress = Wormhole.chainAddress(
261+
request.source.id.chain,
262+
configuredAddress
263+
);
264+
}
227265
}
228266

229267
const validatedParams: Vp = {
@@ -233,6 +271,7 @@ export class NttExecutorRoute<N extends Network>
233271
sourceContracts: srcContracts,
234272
destinationContracts: dstContracts,
235273
referrerFeeDbps,
274+
referrerAddress,
236275
},
237276
options,
238277
};
@@ -326,16 +365,9 @@ export class NttExecutorRoute<N extends Network>
326365
): Promise<NttWithExecutor.Quote> {
327366
const { fromChain, toChain } = request;
328367

329-
let referrer = getDefaultReferrerAddress(fromChain.chain);
330-
const referrerFeeConfig = this.staticConfig.referrerFee;
331-
if (referrerFeeConfig) {
332-
const platform = chainToPlatform(fromChain.chain);
333-
const referrerAddress =
334-
referrerFeeConfig.referrerAddresses?.[platform] ?? "";
335-
if (referrerAddress) {
336-
referrer = Wormhole.chainAddress(fromChain.chain, referrerAddress);
337-
}
338-
}
368+
const referrer =
369+
params.normalizedParams.referrerAddress ??
370+
getDefaultReferrerAddress(fromChain.chain);
339371

340372
const { referrerFee, remainingAmount, referrerFeeDbps } =
341373
calculateReferrerFee(

0 commit comments

Comments
 (0)