Skip to content

Commit 6fd1608

Browse files
committed
sdk: multi transceiver support
1 parent e10d936 commit 6fd1608

File tree

6 files changed

+365
-254
lines changed

6 files changed

+365
-254
lines changed

evm/ts/src/ntt.ts

+93-59
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,26 @@ export class EvmNttWormholeTranceiver<N extends Network, C extends EvmChains>
6161
}
6262

6363
async getTransceiverType(): Promise<string> {
64-
// NOTE: We hardcode the type here as transceiver type is only available for versions >1.1.0
65-
// For those versions, we can return `this.transceiver.getTransceiverType()` directly
66-
return "wormhole";
64+
const contract = new Contract(
65+
this.address,
66+
["function getTransceiverType() public view returns (string)"],
67+
this.manager.provider
68+
);
69+
try {
70+
const transceiverType: string = await contract
71+
.getFunction("getTransceiverType")
72+
.staticCall();
73+
if (!transceiverType) {
74+
throw new Error("getTransceiverType not found");
75+
}
76+
return Buffer.from(transceiverType).filter((x) => x !== 0).toString().trim();
77+
} catch (e) {
78+
console.error(e); // TODO: only when the method is not available should we return wormhole
79+
// NOTE: if the transceiver doesn't have a getTransceiverType method, it
80+
// means it's an old one that didn't have this function exposed.
81+
// Only wormhole transceivers were deployed using the old version
82+
return "wormhole"
83+
}
6784
}
6885

6986
getAddress(): ChainAddress<C> {
@@ -143,6 +160,16 @@ export class EvmNttWormholeTranceiver<N extends Network, C extends EvmChains>
143160
);
144161
}
145162

163+
async isRelayingAvailable(destination: Chain): Promise<boolean> {
164+
// TODO: how to handle executor?
165+
const [wh, special] = await Promise.all([
166+
this.isWormholeRelayingEnabled(destination),
167+
this.isSpecialRelayingEnabled(destination)
168+
])
169+
170+
return wh || special
171+
}
172+
146173
async isWormholeRelayingEnabled(destChain: Chain): Promise<boolean> {
147174
return await this.transceiver.isWormholeRelayingEnabled(
148175
toChainId(destChain)
@@ -186,7 +213,7 @@ export class EvmNtt<N extends Network, C extends EvmChains>
186213
tokenAddress: string;
187214
readonly chainId: bigint;
188215
manager: NttManagerBindings.NttManager;
189-
xcvrs: EvmNttWormholeTranceiver<N, C>[];
216+
xcvrs: { [type: string]: EvmNttTransceiver<N, C, Ntt.Attestation> };
190217
managerAddress: string;
191218

192219
constructor(
@@ -213,38 +240,52 @@ export class EvmNtt<N extends Network, C extends EvmChains>
213240
this.provider
214241
);
215242

216-
this.xcvrs = [];
217-
if (
218-
"wormhole" in contracts.ntt.transceiver &&
219-
contracts.ntt.transceiver["wormhole"]
220-
) {
221-
const transceiverTypes = [
222-
"wormhole", // wormhole xcvr should be ix 0
223-
...Object.keys(contracts.ntt.transceiver).filter((transceiverType) => {
224-
transceiverType !== "wormhole";
225-
}),
226-
];
227-
transceiverTypes.map((transceiverType) => {
228-
// we currently only support wormhole transceivers
229-
if (transceiverType !== "wormhole") {
230-
throw new Error(`Unsupported transceiver type: ${transceiverType}`);
231-
}
243+
this.xcvrs = {};
244+
Object.entries(contracts.ntt.transceiver).map(([transceiverType, transceiver]) => {
245+
// we currently only support wormhole transceivers
246+
if (!["wormhole", "paxos"].includes(transceiverType)) {
247+
throw new Error(`Unsupported transceiver type: ${transceiverType}`);
248+
}
232249

233-
// Enable more Transceivers here
234-
this.xcvrs.push(
235-
new EvmNttWormholeTranceiver(
236-
this,
237-
contracts.ntt!.transceiver[transceiverType]!,
238-
abiBindings!
239-
)
250+
// Enable more Transceivers here
251+
this.xcvrs[transceiverType] =
252+
new EvmNttWormholeTranceiver(
253+
this,
254+
transceiver instanceof Object ? transceiver.address : transceiver,
255+
abiBindings!
240256
);
241-
});
242-
}
257+
});
243258
}
244259

245-
async getTransceiver(ix: number): Promise<NttTransceiver<N, C, any> | null> {
260+
async getTransceiver(type: string): Promise<NttTransceiver<N, C, any> | null> {
246261
// TODO: should we make an RPC call here, or just trust that the xcvrs are set up correctly?
247-
return this.xcvrs[ix] || null;
262+
return this.xcvrs[type] || null;
263+
}
264+
265+
async getTransceivers(): Promise<{ [type: string]: NttTransceiver<N, C, any> }> {
266+
return this.xcvrs
267+
}
268+
269+
async isTransceiverRegistered(type: string): Promise<boolean> {
270+
const transceiver = this.xcvrs[type];
271+
if (!transceiver) {
272+
throw new Error(`Transceiver not found (${type})`);
273+
}
274+
const transceivers = await this.manager.getTransceivers();
275+
return transceivers.map((a) => new EvmAddress(a).unwrap()).includes(new EvmAddress(transceiver.getAddress().address).unwrap());
276+
}
277+
278+
async *registerTransceiver(type: string) {
279+
const transceiver = this.xcvrs[type];
280+
if (!transceiver) {
281+
throw new Error(`Transceiver not found (${type})`);
282+
}
283+
284+
const canonicalTransceiver = new EvmAddress(transceiver.getAddress().address).toString();
285+
const tx = await this.manager.setTransceiver.populateTransaction(
286+
canonicalTransceiver
287+
);
288+
yield this.createUnsignedTx(tx, "Ntt.registerTransceiver");
248289
}
249290

250291
async getMode(): Promise<Ntt.Mode> {
@@ -290,19 +331,18 @@ export class EvmNtt<N extends Network, C extends EvmChains>
290331
yield this.createUnsignedTx(tx, "Ntt.setPauser");
291332
}
292333

334+
async *setThreshold(threshold: number) {
335+
const tx = await this.manager.setThreshold.populateTransaction(threshold);
336+
yield this.createUnsignedTx(tx, "Ntt.setThreshold");
337+
}
338+
293339
async getThreshold(): Promise<number> {
294340
return Number(await this.manager.getThreshold());
295341
}
296342

297343
async isRelayingAvailable(destination: Chain): Promise<boolean> {
298344
const enabled = await Promise.all(
299-
this.xcvrs.map(async (x) => {
300-
const [wh, special] = await Promise.all([
301-
x.isWormholeRelayingEnabled(destination),
302-
x.isSpecialRelayingEnabled(destination),
303-
]);
304-
return wh || special;
305-
})
345+
Object.values(this.xcvrs).map((x) => x.isRelayingAvailable(destination))
306346
);
307347

308348
return enabled.filter((x) => x).length > 0;
@@ -387,7 +427,7 @@ export class EvmNtt<N extends Network, C extends EvmChains>
387427

388428
ixs.push({
389429
index: 0,
390-
payload: this.xcvrs[0]!.encodeFlags({ skipRelay: !options.automatic }),
430+
payload: (this.xcvrs["wormhole"]! as EvmNttWormholeTranceiver<N, C>).encodeFlags({ skipRelay: !options.automatic }),
391431
});
392432

393433
return ixs;
@@ -448,14 +488,14 @@ export class EvmNtt<N extends Network, C extends EvmChains>
448488
}
449489

450490
async *setWormholeTransceiverPeer(peer: ChainAddress<C>) {
451-
yield* this.setTransceiverPeer(0, peer);
491+
yield* this.setTransceiverPeer("wormhole", peer);
452492
}
453493

454-
async *setTransceiverPeer(ix: number, peer: ChainAddress<C>) {
455-
if (ix >= this.xcvrs.length) {
456-
throw new Error("Transceiver not found");
494+
async *setTransceiverPeer(type: string, peer: ChainAddress<C>) {
495+
if (!this.xcvrs[type]) {
496+
throw new Error(`Transceiver not found (${type})`);
457497
}
458-
yield* this.xcvrs[ix]!.setPeer(peer);
498+
yield* this.xcvrs[type]!.setPeer(peer);
459499
}
460500

461501
async *transfer(
@@ -506,22 +546,15 @@ export class EvmNtt<N extends Network, C extends EvmChains>
506546
yield this.createUnsignedTx(addFrom(txReq, senderAddress), "Ntt.transfer");
507547
}
508548

509-
// TODO: should this be some map of idx to transceiver?
510-
async *redeem(attestations: Ntt.Attestation[]) {
511-
if (attestations.length !== this.xcvrs.length)
512-
throw new Error(
513-
"Not enough attestations for the registered Transceivers"
514-
);
515-
516-
for (const idx in this.xcvrs) {
517-
const xcvr = this.xcvrs[idx]!;
518-
const attestation = attestations[idx];
549+
async *redeem(attestations: { [type: string]: Ntt.Attestation }) {
550+
for (const [transceiverType, transceiver] of Object.entries(this.xcvrs)) {
551+
const attestation = attestations[transceiverType];
519552
if (attestation?.payloadName !== "WormholeTransfer") {
520553
// TODO: support standard relayer attestations
521554
// which must be submitted to the delivery provider
522555
throw new Error("Invalid attestation type for redeem");
523556
}
524-
yield* xcvr.receive(attestation);
557+
yield* transceiver.receive(attestation);
525558
}
526559
}
527560

@@ -605,21 +638,22 @@ export class EvmNtt<N extends Network, C extends EvmChains>
605638
const local: Partial<Ntt.Contracts> = {
606639
manager: this.managerAddress,
607640
token: this.tokenAddress,
608-
transceiver: {
609-
...(this.xcvrs.length > 0 && { wormhole: this.xcvrs[0]!.address }),
610-
},
641+
transceiver: Object.fromEntries(Object.entries(this.xcvrs).map(([type, xcvr]) => [type, { address: xcvr.getAddress().address.toString() }])),
611642
// TODO: what about the quoter?
612643
};
613644

614645
const remote: Partial<Ntt.Contracts> = {
615646
manager: this.managerAddress,
616647
token: await this.manager.token(),
617648
transceiver: {
618-
wormhole: (await this.manager.getTransceivers())[0]!, // TODO: make this more generic
649+
wormhole: {
650+
address: (await this.manager.getTransceivers())[0]!, // TODO: make this more generic
651+
}
619652
},
620653
};
621654

622655
const deleteMatching = (a: any, b: any) => {
656+
if (!b || !a) return;
623657
for (const k in a) {
624658
if (typeof a[k] === "object") {
625659
deleteMatching(a[k], b[k]);

sdk/definitions/src/ntt.ts

+32-9
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
transceiverInstructionLayout,
2626
transceiverRegistration,
2727
} from "./layouts/index.js";
28-
import { PublicKey } from "@solana/web3.js";
28+
import { PublicKey, TransactionInstruction } from "@solana/web3.js";
2929

3030
/**
3131
* @namespace Ntt
@@ -39,7 +39,10 @@ export namespace Ntt {
3939
token: string;
4040
manager: string;
4141
transceiver: {
42-
[type: string]: string;
42+
[type: string]: string | {
43+
address: string;
44+
config?: any // transceiver specific extra information
45+
};
4346
};
4447
quoter?: string;
4548
};
@@ -184,16 +187,20 @@ export interface Ntt<N extends Network, C extends Chain> {
184187

185188
getThreshold(): Promise<number>;
186189

190+
setThreshold(
191+
threshold: number,
192+
payer?: AccountAddress<C>
193+
): AsyncGenerator<UnsignedTransaction<N, C>>;
194+
187195
setPeer(
188196
peer: ChainAddress,
189197
tokenDecimals: number,
190198
inboundLimit: bigint,
191199
payer?: AccountAddress<C>
192200
): AsyncGenerator<UnsignedTransaction<N, C>>;
193201

194-
// TODO: replace ix with transceiver type
195202
setTransceiverPeer(
196-
ix: number,
203+
type: string,
197204
peer: ChainAddress,
198205
payer?: AccountAddress<C>
199206
): AsyncGenerator<UnsignedTransaction<N, C>>;
@@ -235,7 +242,7 @@ export interface Ntt<N extends Network, C extends Chain> {
235242
* TODO: replace with Map<transceiver type, Attestation>
236243
*/
237244
redeem(
238-
attestations: Ntt.Attestation[],
245+
attestations: { [type: string]: Ntt.Attestation },
239246
payer?: AccountAddress<C>
240247
): AsyncGenerator<UnsignedTransaction<N, C>>;
241248

@@ -248,14 +255,26 @@ export interface Ntt<N extends Network, C extends Chain> {
248255
/** Get the peer information for the given chain if it exists */
249256
getPeer<C extends Chain>(chain: C): Promise<Ntt.Peer<C> | null>;
250257

251-
/** Get the transceiver corresponding to index (0 = Wormhole)
252-
*
253-
* TODO: replace ix with transceiver type
258+
/** Get the transceiver corresponding to type
254259
*/
255260
getTransceiver(
256-
ix: number
261+
type: string // TODO: make a better type than string (maybe enum?) also the attestation type needs to depend on the transceiver type
257262
): Promise<NttTransceiver<N, C, Ntt.Attestation> | null>;
258263

264+
getTransceivers(): Promise<{ [type: string]: NttTransceiver<N, C, any> }>;
265+
266+
/**
267+
* Returns whether the transceiver is registered in the manager.
268+
* The address of the transceiver is set at construction time on the Ntt object.
269+
*/
270+
isTransceiverRegistered(type: string): Promise<boolean>;
271+
272+
/**
273+
* Register a transceiver.
274+
* The address of the transceiver is set at construction time on the Ntt object.
275+
*/
276+
registerTransceiver(type: string, payer?: AccountAddress<C>): AsyncGenerator<UnsignedTransaction<N, C>>;
277+
259278
/**
260279
* getCurrentOutboundCapacity returns the current outbound capacity of the Ntt manager
261280
*/
@@ -396,6 +415,8 @@ export interface NttTransceiver<
396415
attestation: A,
397416
sender?: AccountAddress<C>
398417
): AsyncGenerator<UnsignedTransaction<N, C>>;
418+
419+
isRelayingAvailable(destination: Chain): Promise<boolean>;
399420
}
400421

401422
export namespace WormholeNttTransceiver {
@@ -425,6 +446,8 @@ export interface SolanaNttTransceiver<
425446
A extends Ntt.Attestation
426447
> extends NttTransceiver<N, C, A> {
427448
programId: PublicKey;
449+
450+
createReleaseIx(outboxItem: PublicKey, revertOnDelay: boolean, payer: PublicKey): Promise<TransactionInstruction>;
428451
}
429452

430453
export interface EvmNttTransceiver<

sdk/route/src/automatic.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,9 @@ export class NttAutomaticRoute<N extends Network>
318318
token,
319319
manager,
320320
transceiver: {
321-
wormhole: whTransceiver,
321+
wormhole: {
322+
address: whTransceiver
323+
},
322324
},
323325
},
324326
destinationContracts: {

sdk/route/src/manual.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ export class NttManualRoute<N extends Network>
225225
ntt: receipt.params.normalizedParams.destinationContracts,
226226
});
227227
const sender = Wormhole.parseAddress(signer.chain(), signer.address());
228-
const completeXfer = ntt.redeem([receipt.attestation.attestation], sender);
228+
const completeXfer = ntt.redeem({ wormhole: receipt.attestation.attestation }, sender);
229229

230230
const txids = await signSendWait(toChain, completeXfer, signer);
231231
return {
@@ -297,7 +297,9 @@ export class NttManualRoute<N extends Network>
297297
token,
298298
manager,
299299
transceiver: {
300-
wormhole: whTransceiver,
300+
wormhole: {
301+
address: whTransceiver
302+
},
301303
},
302304
},
303305
destinationContracts: {

0 commit comments

Comments
 (0)