Skip to content
This repository was archived by the owner on Jun 16, 2025. It is now read-only.

Commit c2af1f9

Browse files
committed
start to add token router
1 parent e6f3d3b commit c2af1f9

File tree

6 files changed

+181
-16
lines changed

6 files changed

+181
-16
lines changed

solana/Anchor.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ cluster = "Localnet"
3131
wallet = "ts/tests/keys/pFCBP4bhqdSsrWUVTgqhPsLrfEdChBK17vgFM7TxjxQ.json"
3232

3333
[scripts]
34-
test-local = "npx ts-mocha -p ./tsconfig.anchor.json -t 1000000 --bail --full-trace --exit ts/tests/01*.ts"
35-
# test-local = "npx ts-mocha -p ./tsconfig.anchor.json -t 1000000 --bail --full-trace --exit ts/tests/0[0-9]*.ts"
34+
test-local = "npx ts-mocha -p ./tsconfig.anchor.json -t 1000000 --bail --full-trace --exit ts/tests/0[0-9]*.ts"
3635
test-upgrade-fork = "npx ts-mocha -p ./tsconfig.anchor.json -t 1000000 --bail --full-trace --exit ts/tests/1[0-9]*.ts"
3736

3837
[test]

solana/ts/src/protocol/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from "./matchingEngine";
2+
export * from "./tokenRouter";

solana/ts/src/protocol/matchingEngine.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,14 @@ export class SolanaMatchingEngine<N extends Network, C extends SolanaChains>
9494
},
9595
params,
9696
);
97-
const transaction = await this.createTx(new SolanaAddress(owner).unwrap(), [ix]);
97+
const transaction = this.createTx(new SolanaAddress(owner).unwrap(), [ix]);
9898
yield this.createUnsignedTx({ transaction }, "MatchingEngine.initialize");
9999
}
100100

101101
async *setPause(sender: AnySolanaAddress, pause: boolean) {
102102
const payer = new SolanaAddress(sender).unwrap();
103103
const ix = await this.setPauseIx({ ownerOrAssistant: payer }, pause);
104-
const transaction = await this.createTx(payer, [ix]);
104+
const transaction = this.createTx(payer, [ix]);
105105
yield this.createUnsignedTx({ transaction }, "MatchingEngine.setPause");
106106
}
107107

@@ -123,7 +123,7 @@ export class SolanaMatchingEngine<N extends Network, C extends SolanaChains>
123123
{ chain: toChainId(chain), cctpDomain, address, mintRecipient },
124124
);
125125

126-
const transaction = await this.createTx(ownerOrAssistant, [ix]);
126+
const transaction = this.createTx(ownerOrAssistant, [ix]);
127127
yield this.createUnsignedTx({ transaction }, "MatchingEngine.registerRouter");
128128
}
129129

@@ -144,7 +144,7 @@ export class SolanaMatchingEngine<N extends Network, C extends SolanaChains>
144144
{ chain: toChainId(chain), cctpDomain, address, mintRecipient },
145145
);
146146

147-
const transaction = await this.createTx(owner, [ix]);
147+
const transaction = this.createTx(owner, [ix]);
148148
yield this.createUnsignedTx({ transaction }, "MatchingEngine.updateRouter");
149149
}
150150

@@ -153,7 +153,7 @@ export class SolanaMatchingEngine<N extends Network, C extends SolanaChains>
153153

154154
const ix = await this.disableRouterEndpointIx({ owner }, toChainId(chain));
155155

156-
const transaction = await this.createTx(owner, [ix]);
156+
const transaction = this.createTx(owner, [ix]);
157157
yield this.createUnsignedTx({ transaction }, "MatchingEngine.disableRouter");
158158
}
159159

@@ -187,7 +187,7 @@ export class SolanaMatchingEngine<N extends Network, C extends SolanaChains>
187187
{ offerPrice, totalDeposit },
188188
);
189189

190-
const transaction = await this.createTx(payer, ixs);
190+
const transaction = this.createTx(payer, ixs);
191191
yield this.createUnsignedTx({ transaction }, "MatchingEngine.placeInitialOffer");
192192
}
193193

@@ -203,7 +203,7 @@ export class SolanaMatchingEngine<N extends Network, C extends SolanaChains>
203203

204204
const ixs = await this.improveOfferIx({ participant, auction }, { offerPrice: offer });
205205

206-
const transaction = await this.createTx(participant, ixs);
206+
const transaction = this.createTx(participant, ixs);
207207
yield this.createUnsignedTx({ transaction }, "MatchingEngine.improveOffer");
208208
}
209209

@@ -256,7 +256,7 @@ export class SolanaMatchingEngine<N extends Network, C extends SolanaChains>
256256
units: 300_000,
257257
});
258258

259-
const transaction = await this.createTx(payer, [ix, computeIx]);
259+
const transaction = this.createTx(payer, [ix, computeIx]);
260260
yield this.createUnsignedTx({ transaction }, "MatchingEngine.executeFastOrder");
261261
}
262262

@@ -310,7 +310,7 @@ export class SolanaMatchingEngine<N extends Network, C extends SolanaChains>
310310

311311
const computeIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 });
312312

313-
const transaction = await this.createTx(payer, [ix, computeIx], lookupTables);
313+
const transaction = this.createTx(payer, [ix, computeIx], lookupTables);
314314
yield this.createUnsignedTx({ transaction }, "MatchingEngine.prepareOrderResponse");
315315
}
316316

@@ -377,16 +377,15 @@ export class SolanaMatchingEngine<N extends Network, C extends SolanaChains>
377377

378378
ixs.push(settleIx);
379379

380-
const transaction = await this.createTx(payer, ixs, lookupTables);
381-
380+
const transaction = this.createTx(payer, ixs, lookupTables);
382381
yield this.createUnsignedTx({ transaction }, "MatchingEngine.settleAuctionComplete");
383382
}
384383

385-
private async createTx(
384+
private createTx(
386385
payerKey: PublicKey,
387386
instructions: TransactionInstruction[],
388387
lookupTables?: AddressLookupTableAccount[],
389-
): Promise<VersionedTransaction> {
388+
): VersionedTransaction {
390389
const messageV0 = new TransactionMessage({
391390
payerKey,
392391
recentBlockhash: "",
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import {
2+
AddressLookupTableAccount,
3+
Connection,
4+
PublicKey,
5+
TransactionInstruction,
6+
TransactionMessage,
7+
VersionedTransaction,
8+
} from "@solana/web3.js";
9+
import {
10+
FastTransfer,
11+
TokenRouter,
12+
} from "@wormhole-foundation/example-liquidity-layer-definitions";
13+
import { Chain, Network, Platform } from "@wormhole-foundation/sdk-base";
14+
import {
15+
AccountAddress,
16+
ChainAddress,
17+
ChainsConfig,
18+
CircleBridge,
19+
Contracts,
20+
UnsignedTransaction,
21+
VAA,
22+
} from "@wormhole-foundation/sdk-definitions";
23+
import {
24+
AnySolanaAddress,
25+
SolanaAddress,
26+
SolanaChains,
27+
SolanaPlatform,
28+
SolanaTransaction,
29+
SolanaUnsignedTransaction,
30+
} from "@wormhole-foundation/sdk-solana";
31+
import { SolanaWormholeCore } from "@wormhole-foundation/sdk-solana-core";
32+
import { ProgramId, TokenRouterProgram } from "../tokenRouter";
33+
34+
export interface SolanaTokenRouterContracts {
35+
tokenRouter: string;
36+
usdcMint: string;
37+
}
38+
39+
export class SolanaTokenRouter<N extends Network, C extends SolanaChains>
40+
extends TokenRouterProgram
41+
implements TokenRouter<N, C>
42+
{
43+
coreBridge: SolanaWormholeCore<N, C>;
44+
45+
constructor(
46+
readonly _network: N,
47+
readonly _chain: C,
48+
readonly _connection: Connection,
49+
readonly _contracts: Contracts & SolanaTokenRouterContracts,
50+
) {
51+
super(_connection, _contracts.tokenRouter as ProgramId, new PublicKey(_contracts.usdcMint));
52+
53+
this.coreBridge = new SolanaWormholeCore(_network, _chain, _connection, {
54+
coreBridge: this.coreBridgeProgramId().toBase58(),
55+
...this._contracts,
56+
});
57+
}
58+
59+
static async fromRpc<N extends Network>(
60+
connection: Connection,
61+
config: ChainsConfig<N, Platform>,
62+
contracts: SolanaTokenRouterContracts,
63+
) {
64+
const [network, chain] = await SolanaPlatform.chainFromRpc(connection);
65+
const conf = config[chain]!;
66+
if (conf.network !== network)
67+
throw new Error(`Network mismatch for chain ${chain}: ${conf.network} != ${network}`);
68+
69+
return new SolanaTokenRouter(network as N, chain, connection, {
70+
...config[chain]!.contracts,
71+
...contracts,
72+
});
73+
}
74+
75+
async *initialize(
76+
owner: AnySolanaAddress,
77+
ownerAssistant: AnySolanaAddress,
78+
mint?: AnySolanaAddress,
79+
) {
80+
const sender = new SolanaAddress(owner).unwrap();
81+
const ix = await this.initializeIx({
82+
owner: sender,
83+
ownerAssistant: new SolanaAddress(ownerAssistant).unwrap(),
84+
mint: mint ? new SolanaAddress(mint).unwrap() : undefined,
85+
});
86+
87+
const transaction = this.createTx(sender, [ix]);
88+
yield this.createUnsignedTx({ transaction }, "TokenRouter.Initialize");
89+
}
90+
91+
getInitialAuctionFee(): Promise<bigint> {
92+
throw new Error("Method not implemented.");
93+
}
94+
95+
placeMarketOrder(
96+
amount: bigint,
97+
redeemer: ChainAddress<Chain>,
98+
redeemerMessage: Uint8Array,
99+
minAmountOut?: bigint | undefined,
100+
refundAddress?: AccountAddress<C> | undefined,
101+
): AsyncGenerator<UnsignedTransaction<N, C>, any, unknown> {
102+
throw new Error("Method not implemented.");
103+
}
104+
placeFastMarketOrder<RC extends Chain>(
105+
amount: bigint,
106+
chain: RC,
107+
redeemer: AccountAddress<RC>,
108+
redeemerMessage: Uint8Array,
109+
maxFee: bigint,
110+
deadline: number,
111+
minAmountOut?: bigint | undefined,
112+
refundAddress?: string | undefined,
113+
): AsyncGenerator<UnsignedTransaction<N, C>, any, unknown> {
114+
throw new Error("Method not implemented.");
115+
}
116+
redeemFill(
117+
vaa: FastTransfer.VAA,
118+
cctp: CircleBridge.Attestation,
119+
): AsyncGenerator<UnsignedTransaction<N, C>, any, unknown> {
120+
throw new Error("Method not implemented.");
121+
}
122+
123+
private createTx(
124+
payerKey: PublicKey,
125+
instructions: TransactionInstruction[],
126+
lookupTables?: AddressLookupTableAccount[],
127+
): VersionedTransaction {
128+
const messageV0 = new TransactionMessage({
129+
payerKey,
130+
recentBlockhash: "",
131+
instructions,
132+
}).compileToV0Message(lookupTables);
133+
return new VersionedTransaction(messageV0);
134+
}
135+
136+
private createUnsignedTx(
137+
txReq: SolanaTransaction,
138+
description: string,
139+
parallelizable: boolean = false,
140+
): SolanaUnsignedTransaction<N, C> {
141+
return new SolanaUnsignedTransaction(
142+
txReq,
143+
this._network,
144+
this._chain,
145+
description,
146+
parallelizable,
147+
);
148+
}
149+
}

solana/ts/src/testing/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { SolanaWormholeCore, utils as coreUtils } from "@wormhole-foundation/sdk
1919
import { expect } from "chai";
2020
import { Err, Ok } from "ts-results";
2121
import { CORE_BRIDGE_PID, USDC_MINT_ADDRESS } from "./consts";
22+
import { execSync } from "child_process";
2223

2324
export function toUniversalAddress(address: number[] | Buffer | Array<number>): UniversalAddress {
2425
return new UniversalAddress(new Uint8Array(address));
@@ -218,6 +219,17 @@ export async function postVaa(
218219
return { txids, address };
219220
}
220221

222+
export function loadProgramBpf(artifactPath: string, keypath: string): PublicKey {
223+
// Invoke BPF Loader Upgradeable `write-buffer` instruction.
224+
const buffer = (() => {
225+
const output = execSync(`solana -u l -k ${keypath} program write-buffer ${artifactPath}`);
226+
return new PublicKey(output.toString().match(/^Buffer: ([A-Za-z0-9]+)/)![1]);
227+
})();
228+
229+
// Return the pubkey for the buffer (our new program implementation).
230+
return buffer;
231+
}
232+
221233
export async function waitUntilSlot(connection: Connection, targetSlot: number) {
222234
return new Promise((resolve, _) => {
223235
const sub = connection.onSlotChange((slot) => {

solana/ts/tests/02__tokenRouter.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
toUniversalAddress,
3131
} from "../src/testing";
3232
import { Custodian, PreparedOrder, TokenRouterProgram, localnet } from "../src/tokenRouter";
33+
import { SolanaTokenRouter } from "../src/protocol";
3334

3435
const SOLANA_CHAIN_ID = toChainId("Solana");
3536

@@ -45,7 +46,11 @@ describe("Token Router", function () {
4546
const invalidChain = (foreignChain + 1) as ChainId;
4647
const foreignEndpointAddress = REGISTERED_TOKEN_ROUTERS["Ethereum"]!;
4748
const foreignCctpDomain = 0;
48-
const tokenRouter = new TokenRouterProgram(connection, localnet(), USDC_MINT_ADDRESS);
49+
//const tokenRouter = new TokenRouterProgram(connection, localnet(), USDC_MINT_ADDRESS);
50+
const tokenRouter = new SolanaTokenRouter("Devnet", "Solana", connection, {
51+
tokenRouter: localnet(),
52+
usdcMint: USDC_MINT_ADDRESS.toBase58(),
53+
});
4954

5055
let lookupTableAddress: PublicKey;
5156

0 commit comments

Comments
 (0)