Skip to content

Commit 60c3ed8

Browse files
committed
Added NTT quote warnings, semver ABI support, fixed queued transfer issues
1 parent 1446173 commit 60c3ed8

16 files changed

+443
-261
lines changed

evm/ts/package.json

+9-9
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,19 @@
4444
"test": "jest --config ./jest.config.ts"
4545
},
4646
"dependencies": {
47-
"@wormhole-foundation/sdk-base": "0.8.0-beta.0",
48-
"@wormhole-foundation/sdk-definitions": "0.8.0-beta.0",
47+
"@wormhole-foundation/sdk-base": "0.8.0-beta.2",
48+
"@wormhole-foundation/sdk-definitions": "0.8.0-beta.2",
4949
"@wormhole-foundation/sdk-definitions-ntt": "0.1.0-beta.0",
50-
"@wormhole-foundation/sdk-evm": "0.8.0-beta.0",
51-
"@wormhole-foundation/sdk-evm-core": "0.8.0-beta.0",
50+
"@wormhole-foundation/sdk-evm": "0.8.0-beta.2",
51+
"@wormhole-foundation/sdk-evm-core": "0.8.0-beta.2",
5252
"ethers": "^6.5.1"
5353
},
5454
"peerDependencies": {
5555
"@wormhole-foundation/sdk-definitions-ntt": "0.1.0-beta.0",
56-
"@wormhole-foundation/sdk-base": "0.8.0-beta.0",
57-
"@wormhole-foundation/sdk-definitions": "0.8.0-beta.0",
58-
"@wormhole-foundation/sdk-evm": "0.8.0-beta.0",
59-
"@wormhole-foundation/sdk-evm-core": "0.8.0-beta.0"
56+
"@wormhole-foundation/sdk-base": "0.8.0-beta.2",
57+
"@wormhole-foundation/sdk-definitions": "0.8.0-beta.2",
58+
"@wormhole-foundation/sdk-evm": "0.8.0-beta.2",
59+
"@wormhole-foundation/sdk-evm-core": "0.8.0-beta.2"
6060
},
6161
"devDependencies": {
6262
"@typechain/ethers-v6": "^0.5.1",
@@ -76,4 +76,4 @@
7676
}
7777
}
7878
}
79-
}
79+
}

evm/ts/src/bindings.ts

+18-12
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import { Provider } from "ethers";
22

33
import { _0_1_0, _1_0_0, _1_1_0 } from "./ethers-contracts/index.js";
4-
5-
export const AbiVersions = {
6-
"0.1.0": _0_1_0,
7-
"1.0.0": _1_0_0,
8-
"1.1.0": _1_1_0,
9-
default: _1_1_0,
10-
} as const;
11-
export type AbiVersion = keyof typeof AbiVersions;
4+
import { Ntt } from "@wormhole-foundation/sdk-definitions-ntt";
5+
6+
// This is a descending list of all ABI versions the SDK is aware of.
7+
// We check for the first match in descending order, allowing for higher minor and patch versions
8+
// being used by the live contract (these are supposed to still be compatible with older ABIs).
9+
export const abiVersions = [
10+
["1.1.0", _1_1_0],
11+
["1.0.0", _1_0_0],
12+
["0.1.0", _0_1_0],
13+
] as const;
14+
export type AbiVersion = (typeof abiVersions)[number][0];
1215

1316
export interface NttBindings {
1417
NttManager: NttManagerBindings;
@@ -36,8 +39,11 @@ export interface NttManagerBindings {
3639
connect(address: string, provider: Provider): NttManagerBindings.NttManager;
3740
}
3841

39-
export function loadAbiVersion(version: string) {
40-
if (!(version in AbiVersions))
41-
throw new Error(`Unknown ABI version: ${version}`);
42-
return AbiVersions[version as AbiVersion];
42+
export function loadAbiVersion(targetVersion: string) {
43+
for (const [abiVersion, abi] of abiVersions) {
44+
if (Ntt.abiVersionMatches(targetVersion, abiVersion)) {
45+
return abi;
46+
}
47+
}
48+
throw new Error(`Unknown ABI version: ${targetVersion}`);
4349
}

evm/ts/src/ntt.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export class EvmNtt<N extends Network, C extends EvmChains>
9898
readonly chain: C,
9999
readonly provider: Provider,
100100
readonly contracts: Contracts & { ntt?: Ntt.Contracts },
101-
readonly version: string = "default"
101+
readonly version: string = "1.0.0"
102102
) {
103103
if (!contracts.ntt) throw new Error("No Ntt Contracts provided");
104104

@@ -366,9 +366,10 @@ export class EvmNtt<N extends Network, C extends EvmChains>
366366
transceiverMessage: Ntt.Message,
367367
payer?: AccountAddress<C>
368368
) {
369-
const tx = await this.manager.completeInboundQueuedTransfer(
370-
Ntt.messageDigest(fromChain, transceiverMessage)
371-
);
369+
const tx =
370+
await this.manager.completeInboundQueuedTransfer.populateTransaction(
371+
Ntt.messageDigest(fromChain, transceiverMessage)
372+
);
372373
yield this.createUnsignedTx(tx, "Ntt.completeInboundQueuedTransfer");
373374
}
374375

package-lock.json

+189-188
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@
1717
"version": "tsx setSdkVersion.ts"
1818
},
1919
"devDependencies": {
20+
"@wormhole-foundation/sdk": "0.8.0-beta.2",
2021
"@solana/spl-token": "0.3.9",
2122
"@solana/web3.js": "1.91.7",
2223
"@types/jest": "^29.5.12",
2324
"@types/node": "^20.12.2",
24-
"@wormhole-foundation/sdk": "0.8.0-beta.0",
2525
"@wormhole-foundation/wormchain-sdk": "^0.0.1",
2626
"ethers": "^6.5.1",
2727
"ts-jest": "^29.1.2",

sdk/definitions/package.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@
4949
"test": "jest --config ./jest.config.ts"
5050
},
5151
"dependencies": {
52-
"@wormhole-foundation/sdk-base": "0.8.0-beta.0",
53-
"@wormhole-foundation/sdk-definitions": "0.8.0-beta.0"
52+
"@wormhole-foundation/sdk-base": "0.8.0-beta.2",
53+
"@wormhole-foundation/sdk-definitions": "0.8.0-beta.2"
5454
},
5555
"peerDependencies": {
56-
"@wormhole-foundation/sdk-base": "0.8.0-beta.0",
57-
"@wormhole-foundation/sdk-definitions": "0.8.0-beta.0"
56+
"@wormhole-foundation/sdk-base": "0.8.0-beta.2",
57+
"@wormhole-foundation/sdk-definitions": "0.8.0-beta.2"
5858
},
5959
"type": "module"
60-
}
60+
}

sdk/definitions/src/ntt.ts

+30
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,31 @@ export namespace Ntt {
116116
)
117117
);
118118
}
119+
120+
// Checks for compatibility between the Contract version in use on chain,
121+
// and the ABI version the SDK has. Major version must match, minor version on chain
122+
// should be gte SDK's ABI version.
123+
//
124+
// For example, if the contract is using 1.1.0, we would use 1.0.0 but not 1.2.0.
125+
export function abiVersionMatches(
126+
targetVersion: string,
127+
abiVersion: string
128+
): boolean {
129+
const parseVersion = (version: string) => {
130+
// allow optional tag on patch version
131+
const versionRegex = /^(\d+)\.(\d+)\.(.*)$/;
132+
const match = version.match(versionRegex);
133+
if (!match) {
134+
throw new Error(`Invalid version format: ${version}`);
135+
}
136+
const [, major, minor, patchAndTag] = match;
137+
return { major: Number(major), minor: Number(minor), patchAndTag };
138+
};
139+
const { major: majorTarget, minor: minorTarget } =
140+
parseVersion(targetVersion);
141+
const { major: majorAbi, minor: minorAbi } = parseVersion(abiVersion);
142+
return majorTarget === majorAbi && minorTarget >= minorAbi;
143+
}
119144
}
120145

121146
/**
@@ -194,6 +219,11 @@ export interface Ntt<N extends Network, C extends Chain> {
194219
*/
195220
getCurrentInboundCapacity(fromChain: Chain): Promise<bigint>;
196221

222+
/**
223+
* getRateLimitDuration returns the duration of the rate limit for queued transfers in seconds
224+
*/
225+
getRateLimitDuration(): Promise<bigint>;
226+
197227
/**
198228
* getIsApproved returns whether an attestation is approved
199229
* an attestation is approved when it has been validated but has not necessarily

sdk/examples/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@
3232
"tsx": "^4.7.2"
3333
},
3434
"dependencies": {
35-
"@wormhole-foundation/sdk": "0.8.0-beta.0",
35+
"@wormhole-foundation/sdk": "0.8.0-beta.2",
3636
"@wormhole-foundation/sdk-definitions-ntt": "0.1.0-beta.0",
3737
"@wormhole-foundation/sdk-evm-ntt": "0.1.0-beta.0",
3838
"@wormhole-foundation/sdk-solana-ntt": "0.1.0-beta.0",
3939
"@wormhole-foundation/sdk-route-ntt": "0.1.0-beta.0"
4040
}
41-
}
41+
}

sdk/route/package.json

+6-5
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,16 @@
4545
"ts-node": "^10.9.2"
4646
},
4747
"dependencies": {
48-
"@wormhole-foundation/sdk-connect": "0.8.0-beta.0",
4948
"@wormhole-foundation/sdk-definitions-ntt": "0.1.0-beta.0",
49+
"@wormhole-foundation/sdk-solana-ntt": "0.1.0-beta.0",
5050
"@wormhole-foundation/sdk-evm-ntt": "0.1.0-beta.0",
51-
"@wormhole-foundation/sdk-solana-ntt": "0.1.0-beta.0"
51+
"@wormhole-foundation/sdk-connect": "0.8.0-beta.2"
5252
},
5353
"peerDependencies": {
54+
"@wormhole-foundation/sdk-connect": "0.8.0-beta.2",
5455
"@wormhole-foundation/sdk-definitions-ntt": "0.1.0-beta.0",
55-
"@wormhole-foundation/sdk-evm-ntt": "0.1.0-beta.0",
56-
"@wormhole-foundation/sdk-solana-ntt": "0.1.0-beta.0"
56+
"@wormhole-foundation/sdk-solana-ntt": "0.1.0-beta.0",
57+
"@wormhole-foundation/sdk-evm-ntt": "0.1.0-beta.0"
5758
},
5859
"type": "module",
5960
"exports": {
@@ -68,4 +69,4 @@
6869
}
6970
}
7071
}
71-
}
72+
}

sdk/route/src/automatic.ts

+79-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
ChainAddress,
55
ChainContext,
66
CompletedTransferReceipt,
7+
DestinationQueuedTransferReceipt,
78
Network,
89
RedeemedTransferReceipt,
910
Signer,
@@ -14,6 +15,7 @@ import {
1415
amount,
1516
chainToPlatform,
1617
isAttested,
18+
isDestinationQueued,
1719
isRedeemed,
1820
isSourceFinalized,
1921
isSourceInitiated,
@@ -155,7 +157,7 @@ export class NttAutomaticRoute<N extends Network>
155157
params.normalizedParams.options
156158
);
157159

158-
return {
160+
const result: QR = {
159161
success: true,
160162
params,
161163
sourceToken: {
@@ -178,6 +180,28 @@ export class NttAutomaticRoute<N extends Network>
178180
toChain.config.nativeTokenDecimals
179181
),
180182
};
183+
const dstNtt = await toChain.getProtocol("Ntt", {
184+
ntt: params.normalizedParams.destinationContracts,
185+
});
186+
const duration = await dstNtt.getRateLimitDuration();
187+
if (duration > 0n) {
188+
const capacity = await dstNtt.getCurrentInboundCapacity(fromChain.chain);
189+
const dstAmount = amount.parse(
190+
params.amount,
191+
request.destination.decimals
192+
);
193+
if (
194+
NttRoute.isCapacityThresholdExceeded(amount.units(dstAmount), capacity)
195+
) {
196+
result.warnings = [
197+
{
198+
type: "DestinationCapacityWarning",
199+
delayDurationSec: Number(duration),
200+
},
201+
];
202+
}
203+
}
204+
return result;
181205
}
182206

183207
async initiate(
@@ -211,6 +235,41 @@ export class NttAutomaticRoute<N extends Network>
211235
};
212236
}
213237

238+
// Even though this is an automatic route, the transfer may need to be
239+
// manually finalized if it was queued
240+
async finalize(signer: Signer, receipt: R): Promise<R> {
241+
if (!isDestinationQueued(receipt)) {
242+
throw new Error(
243+
"The transfer must be destination queued in order to finalize"
244+
);
245+
}
246+
247+
const {
248+
attestation: { attestation: vaa },
249+
} = receipt;
250+
251+
const toChain = this.wh.getChain(receipt.to);
252+
const ntt = await toChain.getProtocol("Ntt", {
253+
ntt: receipt.params.normalizedParams.destinationContracts,
254+
});
255+
const sender = Wormhole.chainAddress(signer.chain(), signer.address());
256+
const payload =
257+
vaa.payloadName === "WormholeTransfer"
258+
? vaa.payload
259+
: vaa.payload.payload;
260+
const completeTransfer = ntt.completeInboundQueuedTransfer(
261+
receipt.from,
262+
payload["nttManagerPayload"],
263+
sender.address
264+
);
265+
const finalizeTxids = await signSendWait(toChain, completeTransfer, signer);
266+
return {
267+
...receipt,
268+
state: TransferState.DestinationFinalized,
269+
destinationTxs: [...(receipt.destinationTxs ?? []), ...finalizeTxids],
270+
};
271+
}
272+
214273
public override async *track(receipt: R, timeout?: number) {
215274
if (isSourceInitiated(receipt) || isSourceFinalized(receipt)) {
216275
const { txid } = receipt.originTxs[receipt.originTxs.length - 1]!;
@@ -264,12 +323,29 @@ export class NttAutomaticRoute<N extends Network>
264323
}
265324
}
266325

267-
if (isRedeemed(receipt)) {
326+
if (isRedeemed(receipt) || isDestinationQueued(receipt)) {
268327
const {
269328
attestation: { attestation: vaa },
270329
} = receipt;
271330

272-
if (await ntt.getIsExecuted(vaa)) {
331+
const payload =
332+
vaa.payloadName === "WormholeTransfer"
333+
? vaa.payload
334+
: vaa.payload.payload;
335+
const queuedTransfer = await ntt.getInboundQueuedTransfer(
336+
vaa.emitterChain,
337+
payload["nttManagerPayload"]
338+
);
339+
if (queuedTransfer !== null) {
340+
receipt = {
341+
...receipt,
342+
state: TransferState.DestinationQueued,
343+
queueReleaseTime: new Date(
344+
queuedTransfer.rateLimitExpiryTimestamp * 1000
345+
),
346+
} satisfies DestinationQueuedTransferReceipt<NttRoute.AutomaticAttestationReceipt>;
347+
yield receipt;
348+
} else if (await ntt.getIsExecuted(vaa)) {
273349
receipt = {
274350
...receipt,
275351
state: TransferState.DestinationFinalized,

0 commit comments

Comments
 (0)