@across-protocol/sdk 4.3.11 → 4.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/arch/svm/BlockUtils.d.ts +1 -0
- package/dist/cjs/arch/svm/BlockUtils.js +69 -52
- package/dist/cjs/arch/svm/BlockUtils.js.map +1 -1
- package/dist/cjs/arch/svm/SpokeUtils.d.ts +21 -10
- package/dist/cjs/arch/svm/SpokeUtils.js +303 -49
- package/dist/cjs/arch/svm/SpokeUtils.js.map +1 -1
- package/dist/cjs/arch/svm/types.d.ts +7 -0
- package/dist/cjs/arch/svm/utils.d.ts +8 -3
- package/dist/cjs/arch/svm/utils.js +81 -4
- package/dist/cjs/arch/svm/utils.js.map +1 -1
- package/dist/cjs/caching/IPFS/PinataIPFSClient.d.ts +2 -0
- package/dist/cjs/caching/IPFS/PinataIPFSClient.js +6 -0
- package/dist/cjs/caching/IPFS/PinataIPFSClient.js.map +1 -1
- package/dist/cjs/caching/Memory/MemoryCacheClient.d.ts +2 -0
- package/dist/cjs/caching/Memory/MemoryCacheClient.js +6 -0
- package/dist/cjs/caching/Memory/MemoryCacheClient.js.map +1 -1
- package/dist/cjs/clients/BundleDataClient/utils/PoolRebalanceUtils.d.ts +1 -1
- package/dist/cjs/clients/BundleDataClient/utils/PoolRebalanceUtils.js +5 -2
- package/dist/cjs/clients/BundleDataClient/utils/PoolRebalanceUtils.js.map +1 -1
- package/dist/cjs/clients/BundleDataClient/utils/SuperstructUtils.d.ts +8 -8
- package/dist/cjs/clients/HubPoolClient.d.ts +1 -0
- package/dist/cjs/clients/HubPoolClient.js +11 -0
- package/dist/cjs/clients/HubPoolClient.js.map +1 -1
- package/dist/cjs/interfaces/CachingMechanism.d.ts +2 -0
- package/dist/cjs/interfaces/index.d.ts +0 -1
- package/dist/cjs/interfaces/index.js +0 -1
- package/dist/cjs/interfaces/index.js.map +1 -1
- package/dist/esm/arch/svm/BlockUtils.d.ts +7 -0
- package/dist/esm/arch/svm/BlockUtils.js +76 -54
- package/dist/esm/arch/svm/BlockUtils.js.map +1 -1
- package/dist/esm/arch/svm/SpokeUtils.d.ts +51 -10
- package/dist/esm/arch/svm/SpokeUtils.js +294 -8
- package/dist/esm/arch/svm/SpokeUtils.js.map +1 -1
- package/dist/esm/arch/svm/types.d.ts +7 -0
- package/dist/esm/arch/svm/utils.d.ts +35 -3
- package/dist/esm/arch/svm/utils.js +103 -3
- package/dist/esm/arch/svm/utils.js.map +1 -1
- package/dist/esm/caching/IPFS/PinataIPFSClient.d.ts +2 -0
- package/dist/esm/caching/IPFS/PinataIPFSClient.js +6 -0
- package/dist/esm/caching/IPFS/PinataIPFSClient.js.map +1 -1
- package/dist/esm/caching/Memory/MemoryCacheClient.d.ts +2 -0
- package/dist/esm/caching/Memory/MemoryCacheClient.js +6 -0
- package/dist/esm/caching/Memory/MemoryCacheClient.js.map +1 -1
- package/dist/esm/clients/BundleDataClient/utils/PoolRebalanceUtils.d.ts +1 -1
- package/dist/esm/clients/BundleDataClient/utils/PoolRebalanceUtils.js +5 -2
- package/dist/esm/clients/BundleDataClient/utils/PoolRebalanceUtils.js.map +1 -1
- package/dist/esm/clients/BundleDataClient/utils/SuperstructUtils.d.ts +8 -8
- package/dist/esm/clients/HubPoolClient.d.ts +1 -0
- package/dist/esm/clients/HubPoolClient.js +19 -0
- package/dist/esm/clients/HubPoolClient.js.map +1 -1
- package/dist/esm/interfaces/CachingMechanism.d.ts +12 -0
- package/dist/esm/interfaces/index.d.ts +0 -1
- package/dist/esm/interfaces/index.js +0 -1
- package/dist/esm/interfaces/index.js.map +1 -1
- package/dist/types/arch/svm/BlockUtils.d.ts +7 -0
- package/dist/types/arch/svm/BlockUtils.d.ts.map +1 -1
- package/dist/types/arch/svm/SpokeUtils.d.ts +51 -10
- package/dist/types/arch/svm/SpokeUtils.d.ts.map +1 -1
- package/dist/types/arch/svm/types.d.ts +7 -0
- package/dist/types/arch/svm/types.d.ts.map +1 -1
- package/dist/types/arch/svm/utils.d.ts +35 -3
- package/dist/types/arch/svm/utils.d.ts.map +1 -1
- package/dist/types/caching/IPFS/PinataIPFSClient.d.ts +2 -0
- package/dist/types/caching/IPFS/PinataIPFSClient.d.ts.map +1 -1
- package/dist/types/caching/Memory/MemoryCacheClient.d.ts +2 -0
- package/dist/types/caching/Memory/MemoryCacheClient.d.ts.map +1 -1
- package/dist/types/clients/BundleDataClient/utils/PoolRebalanceUtils.d.ts +1 -1
- package/dist/types/clients/BundleDataClient/utils/PoolRebalanceUtils.d.ts.map +1 -1
- package/dist/types/clients/BundleDataClient/utils/SuperstructUtils.d.ts +8 -8
- package/dist/types/clients/HubPoolClient.d.ts +1 -0
- package/dist/types/clients/HubPoolClient.d.ts.map +1 -1
- package/dist/types/interfaces/CachingMechanism.d.ts +12 -0
- package/dist/types/interfaces/CachingMechanism.d.ts.map +1 -1
- package/dist/types/interfaces/index.d.ts +0 -1
- package/dist/types/interfaces/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/arch/svm/BlockUtils.ts +33 -16
- package/src/arch/svm/SpokeUtils.ts +272 -10
- package/src/arch/svm/types.ts +8 -0
- package/src/arch/svm/utils.ts +99 -8
- package/src/caching/IPFS/PinataIPFSClient.ts +8 -0
- package/src/caching/Memory/MemoryCacheClient.ts +8 -0
- package/src/clients/BundleDataClient/utils/PoolRebalanceUtils.ts +5 -2
- package/src/clients/HubPoolClient.ts +23 -0
- package/src/interfaces/CachingMechanism.ts +14 -0
- package/src/interfaces/index.ts +0 -1
- package/dist/cjs/interfaces/PubSubMechanism.d.ts +0 -4
- package/dist/cjs/interfaces/PubSubMechanism.js +0 -3
- package/dist/cjs/interfaces/PubSubMechanism.js.map +0 -1
- package/dist/esm/interfaces/PubSubMechanism.d.ts +0 -14
- package/dist/esm/interfaces/PubSubMechanism.js +0 -2
- package/dist/esm/interfaces/PubSubMechanism.js.map +0 -1
- package/dist/types/interfaces/PubSubMechanism.d.ts +0 -15
- package/dist/types/interfaces/PubSubMechanism.d.ts.map +0 -1
- package/src/interfaces/PubSubMechanism.ts +0 -15
|
@@ -5,6 +5,7 @@ import { isDefined } from "../../utils/TypeGuards";
|
|
|
5
5
|
import { getCurrentTime } from "../../utils/TimeUtils";
|
|
6
6
|
import { CHAIN_IDs } from "../../constants";
|
|
7
7
|
import { SVMProvider } from "./";
|
|
8
|
+
import { getTimestampForSlot } from "./SpokeUtils";
|
|
8
9
|
|
|
9
10
|
interface SVMBlock extends Block {}
|
|
10
11
|
|
|
@@ -23,13 +24,9 @@ export function averageBlockTime(): Pick<BlockTimeAverage, "average" | "blockRan
|
|
|
23
24
|
return averageBlockTimes[CHAIN_IDs.SOLANA];
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
seconds: number,
|
|
28
|
-
cushionPercentage = 0.0,
|
|
29
|
-
_provider: SVMProvider
|
|
30
|
-
): Promise<number> {
|
|
27
|
+
function estimateBlocksElapsed(seconds: number, cushionPercentage = 0.0, _provider: SVMProvider): number {
|
|
31
28
|
const cushionMultiplier = cushionPercentage + 1.0;
|
|
32
|
-
const { average } =
|
|
29
|
+
const { average } = averageBlockTime();
|
|
33
30
|
return Math.floor((seconds * cushionMultiplier) / average);
|
|
34
31
|
}
|
|
35
32
|
|
|
@@ -73,7 +70,7 @@ export class SVMBlockFinder extends BlockFinder<SVMBlock> {
|
|
|
73
70
|
const cushion = 1;
|
|
74
71
|
const incrementDistance = Math.max(
|
|
75
72
|
// Ensure the increment slot distance is _at least_ a single slot to prevent an infinite loop.
|
|
76
|
-
|
|
73
|
+
estimateBlocksElapsed(initialBlock.timestamp - timestamp, cushion, this.provider),
|
|
77
74
|
1
|
|
78
75
|
);
|
|
79
76
|
|
|
@@ -92,15 +89,33 @@ export class SVMBlockFinder extends BlockFinder<SVMBlock> {
|
|
|
92
89
|
return this.findBlock(this.blocks[index - 1], this.blocks[index], timestamp);
|
|
93
90
|
}
|
|
94
91
|
|
|
92
|
+
/**
|
|
93
|
+
* For a given slot, resolve the current or preceding timestamp.
|
|
94
|
+
* Not all Solana slots have an associated block timestamp; in case of no block at the requested slot, the most
|
|
95
|
+
* immediate preceding block timestamp will be used. Note that this may return an eventually-incorrect timestamp for
|
|
96
|
+
* future slots.
|
|
97
|
+
*/
|
|
98
|
+
private async getBlockTime(_slot?: bigint): Promise<{ slot: bigint; timestamp: number }> {
|
|
99
|
+
let timestamp: number | undefined;
|
|
100
|
+
let slot = _slot ?? (await this.provider.getSlot({ commitment: "finalized" }).send());
|
|
101
|
+
|
|
102
|
+
do {
|
|
103
|
+
timestamp = await getTimestampForSlot(this.provider, slot);
|
|
104
|
+
} while (!isDefined(timestamp) && --slot);
|
|
105
|
+
assert(isDefined(timestamp), `Unable to resolve block time for SVM slot ${_slot ?? "latest"}`);
|
|
106
|
+
assert(BigInt(Number(timestamp) === timestamp), `Unexpected SVM block timestamp: ${timestamp}`);
|
|
107
|
+
|
|
108
|
+
return { slot, timestamp: Number(timestamp) };
|
|
109
|
+
}
|
|
110
|
+
|
|
95
111
|
// Grabs the most recent slot and caches it.
|
|
96
112
|
private async getLatestBlock(): Promise<SVMBlock> {
|
|
97
|
-
const
|
|
98
|
-
const estimatedSlotTime = await this.provider.getBlockTime(latestSlot).send();
|
|
113
|
+
const { slot, timestamp } = await this.getBlockTime();
|
|
99
114
|
|
|
100
115
|
// Cast the return type to an SVMBlock.
|
|
101
116
|
const block: SVMBlock = {
|
|
102
|
-
timestamp
|
|
103
|
-
number: Number(
|
|
117
|
+
timestamp,
|
|
118
|
+
number: Number(slot),
|
|
104
119
|
};
|
|
105
120
|
const index = sortedIndexBy(this.blocks, block, "number");
|
|
106
121
|
if (this.blocks[index]?.number !== block.number) this.blocks.splice(index, 0, block);
|
|
@@ -112,18 +127,20 @@ export class SVMBlockFinder extends BlockFinder<SVMBlock> {
|
|
|
112
127
|
let index = sortedIndexBy(this.blocks, { number } as Block, "number");
|
|
113
128
|
if (this.blocks[index]?.number === number) return this.blocks[index]; // Return early if block already exists.
|
|
114
129
|
|
|
115
|
-
|
|
130
|
+
// The resolved slot may be rotated backwards if no timestamp exists at the requested slot.
|
|
131
|
+
const { slot: _slot, timestamp } = await this.getBlockTime(BigInt(number));
|
|
132
|
+
const slot = Number(_slot);
|
|
116
133
|
// Cast the return type to an SVMBlock.
|
|
117
134
|
const block: SVMBlock = {
|
|
118
|
-
timestamp
|
|
119
|
-
number,
|
|
135
|
+
timestamp,
|
|
136
|
+
number: slot,
|
|
120
137
|
};
|
|
121
138
|
|
|
122
139
|
// Recompute the index after the async call since the state of this.blocks could have changed!
|
|
123
|
-
index = sortedIndexBy(this.blocks, { number } as Block, "number");
|
|
140
|
+
index = sortedIndexBy(this.blocks, { number: slot } as Block, "number");
|
|
124
141
|
|
|
125
142
|
// Rerun this check to avoid duplicate insertion.
|
|
126
|
-
if (this.blocks[index]?.number ===
|
|
143
|
+
if (this.blocks[index]?.number === slot) return this.blocks[index];
|
|
127
144
|
this.blocks.splice(index, 0, block); // A simple insert at index.
|
|
128
145
|
return block;
|
|
129
146
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { SvmSpokeClient } from "@across-protocol/contracts";
|
|
1
|
+
import { MessageTransmitterClient, SvmSpokeClient, TokenMessengerMinterClient } from "@across-protocol/contracts";
|
|
2
2
|
import { decodeFillStatusAccount, fetchState } from "@across-protocol/contracts/dist/src/svm/clients/SvmSpoke";
|
|
3
|
-
import { intToU8Array32 } from "@across-protocol/contracts/dist/src/svm/web3-v1/conversionUtils";
|
|
4
3
|
import { hashNonEmptyMessage } from "@across-protocol/contracts/dist/src/svm/web3-v1";
|
|
4
|
+
import { intToU8Array32 } from "@across-protocol/contracts/dist/src/svm/web3-v1/conversionUtils";
|
|
5
|
+
import { SYSTEM_PROGRAM_ADDRESS } from "@solana-program/system";
|
|
5
6
|
import {
|
|
6
7
|
ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
|
|
7
8
|
TOKEN_PROGRAM_ADDRESS,
|
|
@@ -10,48 +11,60 @@ import {
|
|
|
10
11
|
getCreateAssociatedTokenIdempotentInstruction,
|
|
11
12
|
} from "@solana-program/token";
|
|
12
13
|
import {
|
|
14
|
+
AccountRole,
|
|
13
15
|
Address,
|
|
16
|
+
IAccountMeta,
|
|
17
|
+
KeyPairSigner,
|
|
18
|
+
ReadonlyUint8Array,
|
|
14
19
|
appendTransactionMessageInstruction,
|
|
15
20
|
fetchEncodedAccount,
|
|
16
21
|
fetchEncodedAccounts,
|
|
17
22
|
getAddressEncoder,
|
|
23
|
+
getBase64EncodedWireTransaction,
|
|
18
24
|
getProgramDerivedAddress,
|
|
25
|
+
getSignatureFromTransaction,
|
|
19
26
|
getU32Encoder,
|
|
20
27
|
getU64Encoder,
|
|
21
28
|
pipe,
|
|
22
|
-
|
|
29
|
+
signTransactionMessageWithSigners,
|
|
23
30
|
some,
|
|
24
31
|
type TransactionSigner,
|
|
25
32
|
} from "@solana/kit";
|
|
26
33
|
import assert from "assert";
|
|
27
34
|
import { arrayify, hexZeroPad, hexlify } from "ethers/lib/utils";
|
|
28
35
|
import { Logger } from "winston";
|
|
29
|
-
import {
|
|
36
|
+
import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "../../constants";
|
|
30
37
|
import { DepositWithBlock, FillStatus, FillWithBlock, RelayData, RelayExecutionEventInfo } from "../../interfaces";
|
|
31
38
|
import {
|
|
32
39
|
BigNumber,
|
|
33
40
|
EvmAddress,
|
|
34
|
-
SvmAddress,
|
|
35
41
|
Address as SdkAddress,
|
|
42
|
+
SvmAddress,
|
|
43
|
+
bs58,
|
|
44
|
+
chainIsProd,
|
|
36
45
|
chainIsSvm,
|
|
37
46
|
chunk,
|
|
38
47
|
isUnsafeDepositId,
|
|
39
48
|
keccak256,
|
|
49
|
+
mapAsync,
|
|
40
50
|
toAddressType,
|
|
41
51
|
} from "../../utils";
|
|
42
|
-
import { SvmCpiEventsClient } from "./eventsClient";
|
|
43
52
|
import {
|
|
44
53
|
bigToU8a32,
|
|
45
54
|
createDefaultTransaction,
|
|
55
|
+
getCCTPNoncePda,
|
|
46
56
|
getEventAuthority,
|
|
47
57
|
getFillStatusPda,
|
|
58
|
+
getSelfAuthority,
|
|
48
59
|
getStatePda,
|
|
60
|
+
isDepositForBurnEvent,
|
|
61
|
+
simulateAndDecode,
|
|
49
62
|
toAddress,
|
|
50
63
|
unwrapEventData,
|
|
51
|
-
} from "./
|
|
52
|
-
import {
|
|
53
|
-
import {
|
|
54
|
-
import { SVMEventNames, SVMProvider } from "./types";
|
|
64
|
+
} from "./";
|
|
65
|
+
import { SvmCpiEventsClient } from "./eventsClient";
|
|
66
|
+
import { SVM_NO_BLOCK_AT_SLOT, isSolanaError } from "./provider";
|
|
67
|
+
import { AttestedCCTPMessage, SVMEventNames, SVMProvider } from "./types";
|
|
55
68
|
|
|
56
69
|
/**
|
|
57
70
|
* @note: Average Solana slot duration is about 400-500ms. We can be conservative
|
|
@@ -796,6 +809,19 @@ export const createCloseFillPdaInstruction = async (
|
|
|
796
809
|
);
|
|
797
810
|
};
|
|
798
811
|
|
|
812
|
+
export const createReceiveMessageInstruction = async (
|
|
813
|
+
signer: TransactionSigner,
|
|
814
|
+
solanaClient: SVMProvider,
|
|
815
|
+
input: MessageTransmitterClient.ReceiveMessageInput,
|
|
816
|
+
remainingAccounts: IAccountMeta<string>[]
|
|
817
|
+
) => {
|
|
818
|
+
const receiveMessageIx = await MessageTransmitterClient.getReceiveMessageInstruction(input);
|
|
819
|
+
(receiveMessageIx.accounts as IAccountMeta<string>[]).push(...remainingAccounts);
|
|
820
|
+
return pipe(await createDefaultTransaction(solanaClient, signer), (tx) =>
|
|
821
|
+
appendTransactionMessageInstruction(receiveMessageIx, tx)
|
|
822
|
+
);
|
|
823
|
+
};
|
|
824
|
+
|
|
799
825
|
export async function getAssociatedTokenAddress(
|
|
800
826
|
owner: SvmAddress,
|
|
801
827
|
mint: SvmAddress,
|
|
@@ -1048,3 +1074,239 @@ export async function getFillRelayDelegatePda(
|
|
|
1048
1074
|
|
|
1049
1075
|
return pda;
|
|
1050
1076
|
}
|
|
1077
|
+
|
|
1078
|
+
/**
|
|
1079
|
+
* Checks if a CCTP message has been processed.
|
|
1080
|
+
* @param solanaClient The Solana client.
|
|
1081
|
+
* @param signer The signer of the transaction.
|
|
1082
|
+
* @param nonce The nonce to check.
|
|
1083
|
+
* @param sourceDomain The source domain.
|
|
1084
|
+
* @returns True if the message has been processed, false otherwise.
|
|
1085
|
+
*/
|
|
1086
|
+
export const hasCCTPV1MessageBeenProcessed = async (
|
|
1087
|
+
solanaClient: SVMProvider,
|
|
1088
|
+
signer: KeyPairSigner,
|
|
1089
|
+
nonce: number,
|
|
1090
|
+
sourceDomain: number
|
|
1091
|
+
): Promise<boolean> => {
|
|
1092
|
+
const noncePda = await getCCTPNoncePda(solanaClient, signer, nonce, sourceDomain);
|
|
1093
|
+
const isNonceUsedIx = await MessageTransmitterClient.getIsNonceUsedInstruction({
|
|
1094
|
+
nonce: nonce,
|
|
1095
|
+
usedNonces: noncePda,
|
|
1096
|
+
});
|
|
1097
|
+
const parserFunction = (buf: Buffer): boolean => {
|
|
1098
|
+
if (buf.length != 1) {
|
|
1099
|
+
throw new Error("Invalid buffer length for isNonceUsedIx");
|
|
1100
|
+
}
|
|
1101
|
+
return Boolean(buf[0]);
|
|
1102
|
+
};
|
|
1103
|
+
return await simulateAndDecode(solanaClient, isNonceUsedIx, signer, parserFunction);
|
|
1104
|
+
};
|
|
1105
|
+
|
|
1106
|
+
/**
|
|
1107
|
+
* Returns the account metas for a tokenless message.
|
|
1108
|
+
* @returns The account metas for a tokenless message.
|
|
1109
|
+
*/
|
|
1110
|
+
export async function getAccountMetasForTokenlessMessage(): Promise<IAccountMeta<string>[]> {
|
|
1111
|
+
const statePda = await getStatePda(SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS);
|
|
1112
|
+
return [
|
|
1113
|
+
{ address: statePda, role: AccountRole.READONLY },
|
|
1114
|
+
{ address: await getSelfAuthority(), role: AccountRole.READONLY },
|
|
1115
|
+
{ address: SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS, role: AccountRole.READONLY },
|
|
1116
|
+
{ address: statePda, role: AccountRole.WRITABLE },
|
|
1117
|
+
{ address: await getEventAuthority(SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS), role: AccountRole.READONLY },
|
|
1118
|
+
{ address: SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS, role: AccountRole.READONLY },
|
|
1119
|
+
];
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
/**
|
|
1123
|
+
* Returns the account metas for a deposit message.
|
|
1124
|
+
* @param message The CCTP message.
|
|
1125
|
+
* @param hubChainId The chain ID of the hub.
|
|
1126
|
+
* @param tokenMessengerMinter The token messenger minter address.
|
|
1127
|
+
* @returns The account metas for a deposit message.
|
|
1128
|
+
*/
|
|
1129
|
+
async function getAccountMetasForDepositMessage(
|
|
1130
|
+
message: AttestedCCTPMessage,
|
|
1131
|
+
hubChainId: number,
|
|
1132
|
+
tokenMessengerMinter: Address
|
|
1133
|
+
): Promise<IAccountMeta<string>[]> {
|
|
1134
|
+
const l1Usdc = EvmAddress.from(TOKEN_SYMBOLS_MAP.USDC.addresses[hubChainId]);
|
|
1135
|
+
const l2Usdc = SvmAddress.from(
|
|
1136
|
+
TOKEN_SYMBOLS_MAP.USDC.addresses[chainIsProd(hubChainId) ? CHAIN_IDs.SOLANA : CHAIN_IDs.SOLANA_DEVNET]
|
|
1137
|
+
);
|
|
1138
|
+
|
|
1139
|
+
const [tokenMessengerPda] = await getProgramDerivedAddress({
|
|
1140
|
+
programAddress: tokenMessengerMinter,
|
|
1141
|
+
seeds: ["token_messenger"],
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1144
|
+
const [tokenMinterPda] = await getProgramDerivedAddress({
|
|
1145
|
+
programAddress: tokenMessengerMinter,
|
|
1146
|
+
seeds: ["token_minter"],
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
const [localTokenPda] = await getProgramDerivedAddress({
|
|
1150
|
+
programAddress: tokenMessengerMinter,
|
|
1151
|
+
seeds: ["local_token", bs58.decode(l2Usdc.toBase58())],
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1154
|
+
const [tokenMessengerEventAuthorityPda] = await getProgramDerivedAddress({
|
|
1155
|
+
programAddress: tokenMessengerMinter,
|
|
1156
|
+
seeds: ["__event_authority"],
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
const [custodyTokenAccountPda] = await getProgramDerivedAddress({
|
|
1160
|
+
programAddress: tokenMessengerMinter,
|
|
1161
|
+
seeds: ["custody", bs58.decode(l2Usdc.toBase58())],
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
const state = await getStatePda(SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS);
|
|
1165
|
+
const tokenProgram = TOKEN_PROGRAM_ADDRESS;
|
|
1166
|
+
const vault = await getAssociatedTokenAddress(
|
|
1167
|
+
SvmAddress.from(state),
|
|
1168
|
+
SvmAddress.from(l2Usdc.toBase58()),
|
|
1169
|
+
tokenProgram
|
|
1170
|
+
);
|
|
1171
|
+
|
|
1172
|
+
// Define accounts dependent on deposit information.
|
|
1173
|
+
const [tokenPairPda] = await getProgramDerivedAddress({
|
|
1174
|
+
programAddress: tokenMessengerMinter,
|
|
1175
|
+
seeds: [
|
|
1176
|
+
new Uint8Array(Buffer.from("token_pair")),
|
|
1177
|
+
new Uint8Array(Buffer.from(String(message.sourceDomain))),
|
|
1178
|
+
new Uint8Array(Buffer.from(l1Usdc.toBytes32().slice(2), "hex")),
|
|
1179
|
+
],
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
const [remoteTokenMessengerPda] = await getProgramDerivedAddress({
|
|
1183
|
+
programAddress: tokenMessengerMinter,
|
|
1184
|
+
seeds: ["remote_token_messenger", String(message.sourceDomain)],
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
return [
|
|
1188
|
+
{ address: tokenMessengerPda, role: AccountRole.READONLY },
|
|
1189
|
+
{ address: remoteTokenMessengerPda, role: AccountRole.READONLY },
|
|
1190
|
+
{ address: tokenMinterPda, role: AccountRole.WRITABLE },
|
|
1191
|
+
{ address: localTokenPda, role: AccountRole.WRITABLE },
|
|
1192
|
+
{ address: tokenPairPda, role: AccountRole.READONLY },
|
|
1193
|
+
{ address: vault, role: AccountRole.WRITABLE },
|
|
1194
|
+
{ address: custodyTokenAccountPda, role: AccountRole.WRITABLE },
|
|
1195
|
+
{ address: TOKEN_PROGRAM_ADDRESS, role: AccountRole.READONLY },
|
|
1196
|
+
{ address: tokenMessengerEventAuthorityPda, role: AccountRole.READONLY },
|
|
1197
|
+
{ address: tokenMessengerMinter, role: AccountRole.READONLY },
|
|
1198
|
+
];
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* Returns the CCTP v1 receive message transaction.
|
|
1203
|
+
* @param solanaClient The Solana client.
|
|
1204
|
+
* @param signer The signer of the transaction.
|
|
1205
|
+
* @param message The CCTP message.
|
|
1206
|
+
* @param hubChainId The chain ID of the hub.
|
|
1207
|
+
* @returns The CCTP v1 receive message transaction.
|
|
1208
|
+
*/
|
|
1209
|
+
export async function getCCTPV1ReceiveMessageTx(
|
|
1210
|
+
solanaClient: SVMProvider,
|
|
1211
|
+
signer: KeyPairSigner,
|
|
1212
|
+
message: AttestedCCTPMessage,
|
|
1213
|
+
hubChainId: number
|
|
1214
|
+
) {
|
|
1215
|
+
const [messageTransmitterPda] = await getProgramDerivedAddress({
|
|
1216
|
+
programAddress: MessageTransmitterClient.MESSAGE_TRANSMITTER_PROGRAM_ADDRESS,
|
|
1217
|
+
seeds: ["message_transmitter"],
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
const [eventAuthorityPda] = await getProgramDerivedAddress({
|
|
1221
|
+
programAddress: MessageTransmitterClient.MESSAGE_TRANSMITTER_PROGRAM_ADDRESS,
|
|
1222
|
+
seeds: ["__event_authority"],
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
const cctpMessageReceiver = isDepositForBurnEvent(message)
|
|
1226
|
+
? TokenMessengerMinterClient.TOKEN_MESSENGER_MINTER_PROGRAM_ADDRESS
|
|
1227
|
+
: SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS;
|
|
1228
|
+
|
|
1229
|
+
const [authorityPda] = await getProgramDerivedAddress({
|
|
1230
|
+
programAddress: MessageTransmitterClient.MESSAGE_TRANSMITTER_PROGRAM_ADDRESS,
|
|
1231
|
+
seeds: ["message_transmitter_authority", bs58.decode(cctpMessageReceiver)],
|
|
1232
|
+
});
|
|
1233
|
+
|
|
1234
|
+
// Notice: message.nonce is only valid for v1 messages
|
|
1235
|
+
const usedNonces = await getCCTPNoncePda(solanaClient, signer, message.nonce, message.sourceDomain);
|
|
1236
|
+
|
|
1237
|
+
// Notice: for Svm tokenless messages, we currently only support very specific finalizations: Hub -> Spoke relayRootBundle calls
|
|
1238
|
+
const accountMetas: IAccountMeta<string>[] = isDepositForBurnEvent(message)
|
|
1239
|
+
? await getAccountMetasForDepositMessage(
|
|
1240
|
+
message,
|
|
1241
|
+
hubChainId,
|
|
1242
|
+
TokenMessengerMinterClient.TOKEN_MESSENGER_MINTER_PROGRAM_ADDRESS
|
|
1243
|
+
)
|
|
1244
|
+
: await getAccountMetasForTokenlessMessage();
|
|
1245
|
+
|
|
1246
|
+
const messageBytes = message.messageBytes.startsWith("0x")
|
|
1247
|
+
? Buffer.from(message.messageBytes.slice(2), "hex")
|
|
1248
|
+
: Buffer.from(message.messageBytes, "hex");
|
|
1249
|
+
|
|
1250
|
+
const input: MessageTransmitterClient.ReceiveMessageInput = {
|
|
1251
|
+
program: MessageTransmitterClient.MESSAGE_TRANSMITTER_PROGRAM_ADDRESS,
|
|
1252
|
+
payer: signer,
|
|
1253
|
+
caller: signer,
|
|
1254
|
+
authorityPda,
|
|
1255
|
+
messageTransmitter: messageTransmitterPda,
|
|
1256
|
+
eventAuthority: eventAuthorityPda,
|
|
1257
|
+
usedNonces,
|
|
1258
|
+
receiver: SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS,
|
|
1259
|
+
systemProgram: SYSTEM_PROGRAM_ADDRESS,
|
|
1260
|
+
message: messageBytes,
|
|
1261
|
+
attestation: Buffer.from(message.attestation.slice(2), "hex"),
|
|
1262
|
+
};
|
|
1263
|
+
|
|
1264
|
+
return createReceiveMessageInstruction(signer, solanaClient, input, accountMetas);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
/**
|
|
1268
|
+
* Finalizes CCTP deposits and messages on Solana.
|
|
1269
|
+
*
|
|
1270
|
+
* @param solanaClient The Solana client.
|
|
1271
|
+
* @param attestedMessages The CCTP messages to Solana.
|
|
1272
|
+
* @param signer A base signer to be converted into a Solana signer.
|
|
1273
|
+
* @param simulate Whether to simulate the transaction.
|
|
1274
|
+
* @param hubChainId The chain ID of the hub.
|
|
1275
|
+
* @returns A list of executed transaction signatures.
|
|
1276
|
+
*/
|
|
1277
|
+
|
|
1278
|
+
export function finalizeCCTPV1Messages(
|
|
1279
|
+
solanaClient: SVMProvider,
|
|
1280
|
+
attestedMessages: AttestedCCTPMessage[],
|
|
1281
|
+
signer: KeyPairSigner,
|
|
1282
|
+
simulate = false,
|
|
1283
|
+
hubChainId = 1
|
|
1284
|
+
): Promise<string[]> {
|
|
1285
|
+
return mapAsync(attestedMessages, async (message) => {
|
|
1286
|
+
const receiveMessageIx = await getCCTPV1ReceiveMessageTx(solanaClient, signer, message, hubChainId);
|
|
1287
|
+
|
|
1288
|
+
if (simulate) {
|
|
1289
|
+
const result = await solanaClient
|
|
1290
|
+
.simulateTransaction(
|
|
1291
|
+
getBase64EncodedWireTransaction(await signTransactionMessageWithSigners(receiveMessageIx)),
|
|
1292
|
+
{
|
|
1293
|
+
encoding: "base64",
|
|
1294
|
+
}
|
|
1295
|
+
)
|
|
1296
|
+
.send();
|
|
1297
|
+
if (result.value.err) {
|
|
1298
|
+
throw new Error(result.value.err.toString());
|
|
1299
|
+
}
|
|
1300
|
+
return "";
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
const signedTransaction = await signTransactionMessageWithSigners(receiveMessageIx);
|
|
1304
|
+
const signature = getSignatureFromTransaction(signedTransaction);
|
|
1305
|
+
const encodedTransaction = getBase64EncodedWireTransaction(signedTransaction);
|
|
1306
|
+
await solanaClient
|
|
1307
|
+
.sendTransaction(encodedTransaction, { preflightCommitment: "confirmed", encoding: "base64" })
|
|
1308
|
+
.send();
|
|
1309
|
+
|
|
1310
|
+
return signature;
|
|
1311
|
+
});
|
|
1312
|
+
}
|
package/src/arch/svm/types.ts
CHANGED
|
@@ -61,3 +61,11 @@ export type RpcClient = {
|
|
|
61
61
|
rpc: SVMProvider;
|
|
62
62
|
rpcSubscriptions: RpcSubscriptions<SignatureNotificationsApi & SlotNotificationsApi>;
|
|
63
63
|
};
|
|
64
|
+
|
|
65
|
+
export type AttestedCCTPMessage = {
|
|
66
|
+
nonce: number;
|
|
67
|
+
sourceDomain: number;
|
|
68
|
+
messageBytes: string;
|
|
69
|
+
attestation: string;
|
|
70
|
+
type: "transfer" | "message";
|
|
71
|
+
};
|
package/src/arch/svm/utils.ts
CHANGED
|
@@ -1,24 +1,29 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ethers } from "ethers";
|
|
1
|
+
import { MessageTransmitterClient, SvmSpokeClient } from "@across-protocol/contracts";
|
|
3
2
|
import { BN, BorshEventCoder, Idl } from "@coral-xyz/anchor";
|
|
4
3
|
import {
|
|
5
4
|
Address,
|
|
5
|
+
IInstruction,
|
|
6
|
+
KeyPairSigner,
|
|
6
7
|
address,
|
|
8
|
+
appendTransactionMessageInstruction,
|
|
9
|
+
createTransactionMessage,
|
|
7
10
|
getAddressEncoder,
|
|
11
|
+
getBase64EncodedWireTransaction,
|
|
8
12
|
getProgramDerivedAddress,
|
|
9
|
-
getU64Encoder,
|
|
10
13
|
getU32Encoder,
|
|
14
|
+
getU64Encoder,
|
|
11
15
|
isAddress,
|
|
12
|
-
type TransactionSigner,
|
|
13
16
|
pipe,
|
|
14
|
-
createTransactionMessage,
|
|
15
17
|
setTransactionMessageFeePayerSigner,
|
|
16
18
|
setTransactionMessageLifetimeUsingBlockhash,
|
|
19
|
+
signTransactionMessageWithSigners,
|
|
20
|
+
type TransactionSigner,
|
|
17
21
|
} from "@solana/kit";
|
|
18
|
-
import
|
|
22
|
+
import bs58 from "bs58";
|
|
23
|
+
import { ethers } from "ethers";
|
|
19
24
|
import { FillType, RelayData } from "../../interfaces";
|
|
20
|
-
import { BigNumber, getRelayDataHash, isDefined, isUint8Array
|
|
21
|
-
import { EventName, SVMEventNames, SVMProvider } from "./types";
|
|
25
|
+
import { BigNumber, Address as SdkAddress, getRelayDataHash, isDefined, isUint8Array } from "../../utils";
|
|
26
|
+
import { AttestedCCTPMessage, EventName, SVMEventNames, SVMProvider } from "./types";
|
|
22
27
|
|
|
23
28
|
export { isSolanaError } from "@solana/kit";
|
|
24
29
|
|
|
@@ -334,6 +339,18 @@ export async function getEventAuthority(programId: Address): Promise<Address> {
|
|
|
334
339
|
return eventAuthority;
|
|
335
340
|
}
|
|
336
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Returns the PDA for the Self Authority.
|
|
344
|
+
* @returns The PDA for the Self Authority.
|
|
345
|
+
*/
|
|
346
|
+
export const getSelfAuthority = async () => {
|
|
347
|
+
const [selfAuthority] = await getProgramDerivedAddress({
|
|
348
|
+
programAddress: address(SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS),
|
|
349
|
+
seeds: ["self_authority"],
|
|
350
|
+
});
|
|
351
|
+
return selfAuthority;
|
|
352
|
+
};
|
|
353
|
+
|
|
337
354
|
/**
|
|
338
355
|
* Returns a random SVM address.
|
|
339
356
|
*/
|
|
@@ -358,8 +375,82 @@ export const createDefaultTransaction = async (rpcClient: SVMProvider, signer: T
|
|
|
358
375
|
);
|
|
359
376
|
};
|
|
360
377
|
|
|
378
|
+
/**
|
|
379
|
+
* Simulates a transaction and decodes the result using a parser function.
|
|
380
|
+
* @param solanaClient - The Solana client.
|
|
381
|
+
* @param ix - The instruction to simulate.
|
|
382
|
+
* @param signer - The signer of the transaction.
|
|
383
|
+
* @param parser - The parser function to decode the result.
|
|
384
|
+
* @returns The decoded result.
|
|
385
|
+
*/
|
|
386
|
+
export const simulateAndDecode = async <P extends (buf: Buffer) => unknown>(
|
|
387
|
+
solanaClient: SVMProvider,
|
|
388
|
+
ix: IInstruction,
|
|
389
|
+
signer: KeyPairSigner,
|
|
390
|
+
parser: P
|
|
391
|
+
): Promise<ReturnType<P>> => {
|
|
392
|
+
const simulationTx = appendTransactionMessageInstruction(ix, await createDefaultTransaction(solanaClient, signer));
|
|
393
|
+
|
|
394
|
+
const simulationResult = await solanaClient
|
|
395
|
+
.simulateTransaction(getBase64EncodedWireTransaction(await signTransactionMessageWithSigners(simulationTx)), {
|
|
396
|
+
encoding: "base64",
|
|
397
|
+
})
|
|
398
|
+
.send();
|
|
399
|
+
|
|
400
|
+
if (!simulationResult.value.returnData?.data[0]) {
|
|
401
|
+
throw new Error("No return data");
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return parser(Buffer.from(simulationResult.value.returnData.data[0], "base64")) as ReturnType<P>;
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Returns the PDA for the CCTP nonce.
|
|
409
|
+
* @param solanaClient The Solana client.
|
|
410
|
+
* @param signer The signer of the transaction.
|
|
411
|
+
* @param nonce The nonce to get the PDA for.
|
|
412
|
+
* @param sourceDomain The source domain.
|
|
413
|
+
* @returns The PDA for the CCTP nonce.
|
|
414
|
+
*/
|
|
415
|
+
export const getCCTPNoncePda = async (
|
|
416
|
+
solanaClient: SVMProvider,
|
|
417
|
+
signer: KeyPairSigner,
|
|
418
|
+
nonce: number,
|
|
419
|
+
sourceDomain: number
|
|
420
|
+
) => {
|
|
421
|
+
const [messageTransmitterPda] = await getProgramDerivedAddress({
|
|
422
|
+
programAddress: MessageTransmitterClient.MESSAGE_TRANSMITTER_PROGRAM_ADDRESS,
|
|
423
|
+
seeds: ["message_transmitter"],
|
|
424
|
+
});
|
|
425
|
+
const getNonceIx = await MessageTransmitterClient.getGetNoncePdaInstruction({
|
|
426
|
+
messageTransmitter: messageTransmitterPda,
|
|
427
|
+
nonce,
|
|
428
|
+
sourceDomain: sourceDomain,
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
const parserFunction = (buf: Buffer): Address => {
|
|
432
|
+
if (buf.length === 32) {
|
|
433
|
+
return address(bs58.encode(buf));
|
|
434
|
+
}
|
|
435
|
+
throw new Error("Invalid buffer");
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
return await simulateAndDecode(solanaClient, getNonceIx, signer, parserFunction);
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Checks if a CCTP message is a deposit for burn event.
|
|
443
|
+
* @param event The CCTP message event.
|
|
444
|
+
* @returns True if the message is a deposit for burn event, false otherwise.
|
|
445
|
+
*/
|
|
446
|
+
export function isDepositForBurnEvent(event: AttestedCCTPMessage): boolean {
|
|
447
|
+
return event.type === "transfer";
|
|
448
|
+
}
|
|
449
|
+
|
|
361
450
|
/**
|
|
362
451
|
* Convert a bigint (0 ≤ n < 2^256) to a 32-byte Uint8Array (big-endian).
|
|
452
|
+
* @param n The bigint to convert.
|
|
453
|
+
* @returns The 32-byte Uint8Array.
|
|
363
454
|
*/
|
|
364
455
|
export function bigintToU8a32(n: bigint): Uint8Array {
|
|
365
456
|
if (n < BigInt(0) || n > ethers.constants.MaxUint256.toBigInt()) {
|
|
@@ -89,4 +89,12 @@ export class PinataIPFSClient implements CachingMechanismInterface {
|
|
|
89
89
|
});
|
|
90
90
|
return storeValueInIPFS(key, JSON.stringify(value, jsonReplacerWithBigNumbers), this.client);
|
|
91
91
|
}
|
|
92
|
+
|
|
93
|
+
sub(_topic: string, _callback: (message: string, channel: string) => unknown): Promise<number> {
|
|
94
|
+
return Promise.resolve(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
pub(_topic: string, _message: string) {
|
|
98
|
+
return Promise.resolve(1);
|
|
99
|
+
}
|
|
92
100
|
}
|
|
@@ -32,4 +32,12 @@ export class MemoryCacheClient implements CachingMechanismInterface {
|
|
|
32
32
|
resolve(key);
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
sub(_topic: string, _callback: (message: string, channel: string) => unknown) {
|
|
37
|
+
return Promise.resolve(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pub(_topic: string, _message: string) {
|
|
41
|
+
return Promise.resolve(1);
|
|
42
|
+
}
|
|
35
43
|
}
|
|
@@ -25,7 +25,8 @@ export async function getWidestPossibleExpectedBlockRange(
|
|
|
25
25
|
endBlockBuffers: number[],
|
|
26
26
|
clients: Clients,
|
|
27
27
|
latestMainnetBlock: number,
|
|
28
|
-
enabledChains: number[]
|
|
28
|
+
enabledChains: number[],
|
|
29
|
+
optimistic: boolean = false
|
|
29
30
|
): Promise<number[][]> {
|
|
30
31
|
// We impose a buffer on the head of the chain to increase the probability that the received blocks are final.
|
|
31
32
|
// Reducing the latest block that we query also gives partially filled deposits slightly more buffer for relayers
|
|
@@ -85,7 +86,9 @@ export async function getWidestPossibleExpectedBlockRange(
|
|
|
85
86
|
// Chain has advanced far enough including the buffer, return range from previous proposed end block + 1 to
|
|
86
87
|
// latest block for chain minus buffer.
|
|
87
88
|
return [
|
|
88
|
-
|
|
89
|
+
optimistic
|
|
90
|
+
? clients.hubPoolClient.getOptimisticBundleStartBlockNumber(chainIds, latestMainnetBlock, chainId)
|
|
91
|
+
: clients.hubPoolClient.getNextBundleStartBlockNumber(chainIds, latestMainnetBlock, chainId),
|
|
89
92
|
latestPossibleBundleEndBlockNumbers[index],
|
|
90
93
|
];
|
|
91
94
|
});
|
|
@@ -789,6 +789,29 @@ export class HubPoolClient extends BaseAbstractClient {
|
|
|
789
789
|
return endBlock > 0 ? endBlock + 1 : 0;
|
|
790
790
|
}
|
|
791
791
|
|
|
792
|
+
// @dev Returns the start block of the next bundle assuming that if there is a currently outstanding root bundle proposal, it will pass liveness.
|
|
793
|
+
getOptimisticBundleStartBlockNumber(chainIdList: number[], latestMainnetBlock: number, chainId: number): number {
|
|
794
|
+
// If there is no pending root bundle, then return block ranges based on the latest fully executed root bundle.
|
|
795
|
+
if (!this.hasPendingProposal()) {
|
|
796
|
+
return this.getNextBundleStartBlockNumber(chainIdList, latestMainnetBlock, chainId);
|
|
797
|
+
}
|
|
798
|
+
// We cannot normally index `this.proposedRootBundles` since a bundle there may have been previously disputed, so only index `this.proposedRootBundles`
|
|
799
|
+
// if we have a pending proposal, since this must mean that the pending root bundle is the most recent proposed root bundle.
|
|
800
|
+
const latestProposedBundle = this.proposedRootBundles[this.proposedRootBundles.length - 1];
|
|
801
|
+
|
|
802
|
+
// If there is no previous root bundle, then return 0.
|
|
803
|
+
if (!isDefined(latestProposedBundle)) {
|
|
804
|
+
return 0;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Otherwise, get the bundle end block for the optimistic bundle.
|
|
808
|
+
const optimisticEndBlock = this.getBundleEndBlockForChain(latestProposedBundle, chainId, chainIdList);
|
|
809
|
+
|
|
810
|
+
// As above, this assumes that chain ID's are only added to the chain ID list over time, and that chains are never
|
|
811
|
+
// deleted.
|
|
812
|
+
return optimisticEndBlock > 0 ? optimisticEndBlock + 1 : 0;
|
|
813
|
+
}
|
|
814
|
+
|
|
792
815
|
getLatestExecutedRootBundleContainingL1Token(
|
|
793
816
|
block: number,
|
|
794
817
|
chain: number,
|
|
@@ -34,4 +34,18 @@ export interface CachingMechanismInterface {
|
|
|
34
34
|
ttl?: number,
|
|
35
35
|
overrides?: OverrideType
|
|
36
36
|
): Promise<string | undefined>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Subscribes to a topic.
|
|
40
|
+
* @param topic The topic to subscribe to.
|
|
41
|
+
* @param callback The callback to call when a message is received.
|
|
42
|
+
*/
|
|
43
|
+
sub(topic: string, callback: (message: string, channel: string) => unknown): Promise<number>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Publishes a message to the network.
|
|
47
|
+
* @param topic The topic to publish to.
|
|
48
|
+
* @param message The message to publish.
|
|
49
|
+
*/
|
|
50
|
+
pub(topic: string, message: string): Promise<number>;
|
|
37
51
|
}
|