@chainlink/ccip-sdk 0.97.0 → 1.0.0
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/README.md +12 -9
- package/dist/api/index.d.ts +4 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +16 -4
- package/dist/api/index.js.map +1 -1
- package/dist/aptos/index.d.ts +4 -6
- package/dist/aptos/index.d.ts.map +1 -1
- package/dist/aptos/index.js +0 -5
- package/dist/aptos/index.js.map +1 -1
- package/dist/aptos/logs.d.ts +2 -2
- package/dist/aptos/logs.d.ts.map +1 -1
- package/dist/chain.d.ts +11 -12
- package/dist/chain.d.ts.map +1 -1
- package/dist/chain.js +19 -1
- package/dist/chain.js.map +1 -1
- package/dist/errors/index.d.ts +3 -3
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +3 -3
- package/dist/errors/index.js.map +1 -1
- package/dist/evm/abi/OnRamp_2_0.d.ts +1 -1
- package/dist/evm/abi/OnRamp_2_0.js +1 -1
- package/dist/evm/abi/OnRamp_2_0.js.map +1 -1
- package/dist/evm/index.d.ts +1 -3
- package/dist/evm/index.d.ts.map +1 -1
- package/dist/evm/index.js +1 -5
- package/dist/evm/index.js.map +1 -1
- package/dist/evm/logs.d.ts +1 -1
- package/dist/evm/logs.js +1 -1
- package/dist/evm/offchain.d.ts +1 -14
- package/dist/evm/offchain.d.ts.map +1 -1
- package/dist/evm/offchain.js +1 -133
- package/dist/evm/offchain.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/offchain.d.ts +23 -6
- package/dist/offchain.d.ts.map +1 -1
- package/dist/offchain.js +92 -17
- package/dist/offchain.js.map +1 -1
- package/dist/requests.d.ts.map +1 -1
- package/dist/requests.js +0 -1
- package/dist/requests.js.map +1 -1
- package/dist/solana/idl/1.6.0/BASE_TOKEN_POOL.d.ts +1 -1
- package/dist/solana/idl/1.6.0/BASE_TOKEN_POOL.js +1 -1
- package/dist/solana/idl/1.6.0/BURN_MINT_TOKEN_POOL.d.ts +1 -1
- package/dist/solana/idl/1.6.0/BURN_MINT_TOKEN_POOL.js +1 -1
- package/dist/solana/idl/1.6.0/CCIP_CCTP_TOKEN_POOL.d.ts +1 -1
- package/dist/solana/idl/1.6.0/CCIP_CCTP_TOKEN_POOL.js +1 -1
- package/dist/solana/idl/1.6.0/CCIP_COMMON.d.ts +16 -1
- package/dist/solana/idl/1.6.0/CCIP_COMMON.d.ts.map +1 -1
- package/dist/solana/idl/1.6.0/CCIP_COMMON.js +16 -1
- package/dist/solana/idl/1.6.0/CCIP_COMMON.js.map +1 -1
- package/dist/solana/idl/1.6.0/CCIP_OFFRAMP.d.ts +1 -1
- package/dist/solana/idl/1.6.0/CCIP_OFFRAMP.js +1 -1
- package/dist/solana/idl/1.6.0/CCIP_ROUTER.d.ts +1 -1
- package/dist/solana/idl/1.6.0/CCIP_ROUTER.js +1 -1
- package/dist/solana/index.d.ts +6 -8
- package/dist/solana/index.d.ts.map +1 -1
- package/dist/solana/index.js +2 -7
- package/dist/solana/index.js.map +1 -1
- package/dist/solana/offchain.d.ts +1 -13
- package/dist/solana/offchain.d.ts.map +1 -1
- package/dist/solana/offchain.js +1 -66
- package/dist/solana/offchain.js.map +1 -1
- package/dist/solana/utils.d.ts +4 -4
- package/dist/solana/utils.d.ts.map +1 -1
- package/dist/solana/utils.js +1 -1
- package/dist/solana/utils.js.map +1 -1
- package/dist/sui/index.d.ts +4 -6
- package/dist/sui/index.d.ts.map +1 -1
- package/dist/sui/index.js +0 -5
- package/dist/sui/index.js.map +1 -1
- package/dist/ton/exec.d.ts +2 -2
- package/dist/ton/exec.d.ts.map +1 -1
- package/dist/ton/exec.js +1 -1
- package/dist/ton/exec.js.map +1 -1
- package/dist/ton/index.d.ts +5 -6
- package/dist/ton/index.d.ts.map +1 -1
- package/dist/ton/index.js +0 -4
- package/dist/ton/index.js.map +1 -1
- package/dist/ton/types.d.ts +3 -5
- package/dist/ton/types.d.ts.map +1 -1
- package/dist/ton/types.js.map +1 -1
- package/dist/types.d.ts +10 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +9 -5
- package/src/api/index.ts +21 -8
- package/src/aptos/index.ts +4 -11
- package/src/aptos/logs.ts +2 -2
- package/src/chain.ts +17 -12
- package/src/errors/index.ts +3 -0
- package/src/evm/abi/OnRamp_2_0.ts +1 -1
- package/src/evm/index.ts +4 -10
- package/src/evm/logs.ts +1 -1
- package/src/evm/offchain.ts +2 -191
- package/src/index.ts +8 -1
- package/src/offchain.ts +125 -28
- package/src/requests.ts +2 -3
- package/src/solana/idl/1.6.0/BASE_TOKEN_POOL.ts +2 -2
- package/src/solana/idl/1.6.0/BURN_MINT_TOKEN_POOL.ts +2 -2
- package/src/solana/idl/1.6.0/CCIP_CCTP_TOKEN_POOL.ts +2 -2
- package/src/solana/idl/1.6.0/CCIP_COMMON.ts +32 -2
- package/src/solana/idl/1.6.0/CCIP_OFFRAMP.ts +2 -2
- package/src/solana/idl/1.6.0/CCIP_ROUTER.ts +2 -2
- package/src/solana/index.ts +10 -17
- package/src/solana/offchain.ts +3 -100
- package/src/solana/utils.ts +8 -5
- package/src/sui/index.ts +5 -12
- package/src/ton/exec.ts +3 -6
- package/src/ton/index.ts +10 -15
- package/src/ton/types.ts +3 -6
- package/src/types.ts +13 -10
package/src/offchain.ts
CHANGED
|
@@ -1,56 +1,79 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type BytesLike, dataLength, dataSlice, getBytes, toNumber } from 'ethers'
|
|
2
|
+
import type { PickDeep } from 'type-fest'
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
CCIPLbtcAttestationNotApprovedError,
|
|
5
6
|
CCIPLbtcAttestationNotFoundError,
|
|
6
7
|
CCIPUsdcAttestationError,
|
|
7
8
|
} from './errors/index.ts'
|
|
8
|
-
import {
|
|
9
|
+
import { parseSourceTokenData } from './evm/messages.ts'
|
|
10
|
+
import { type CCIPRequest, type OffchainTokenData, type WithLogger, NetworkType } from './types.ts'
|
|
11
|
+
import { networkInfo } from './utils.ts'
|
|
9
12
|
|
|
10
13
|
const CIRCLE_API_URL = {
|
|
11
|
-
mainnet: 'https://iris-api.circle.com
|
|
12
|
-
testnet: 'https://iris-api-sandbox.circle.com
|
|
14
|
+
mainnet: 'https://iris-api.circle.com',
|
|
15
|
+
testnet: 'https://iris-api-sandbox.circle.com',
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
type CctpAttestationResponse =
|
|
16
19
|
| { error: 'string' }
|
|
17
|
-
| {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
type LombardAttestation =
|
|
26
|
-
| { status: 'NOTARIZATION_STATUS_SESSION_APPROVED'; message_hash: string; attestation: string }
|
|
27
|
-
| { status: string; message_hash: string }
|
|
28
|
-
type LombardAttestationsResponse = { attestations: Array<LombardAttestation> }
|
|
20
|
+
| {
|
|
21
|
+
messages: {
|
|
22
|
+
status: 'pending_confirmations' | 'complete'
|
|
23
|
+
eventNonce?: string
|
|
24
|
+
attestation: string
|
|
25
|
+
message: string
|
|
26
|
+
}[]
|
|
27
|
+
}
|
|
29
28
|
|
|
30
29
|
/**
|
|
31
|
-
* Returns the USDC attestation for a given
|
|
32
|
-
* https://developers.circle.com/
|
|
30
|
+
* Returns the USDC attestation for a given tokenAmount.extraData and txHash
|
|
31
|
+
* https://developers.circle.com/cctp/quickstarts/transfer-usdc-ethereum-to-arc#3-3-retrieve-attestation
|
|
33
32
|
*
|
|
34
|
-
* @param
|
|
33
|
+
* @param opts - CCTPv2 options
|
|
35
34
|
* @param networkType - network type (mainnet or testnet)
|
|
36
|
-
* @returns USDC/CCTP attestation
|
|
35
|
+
* @returns USDC/CCTP attestation and message
|
|
37
36
|
*/
|
|
38
37
|
export async function getUsdcAttestation(
|
|
39
|
-
|
|
38
|
+
opts: {
|
|
39
|
+
/** CCTP sourceDomain */
|
|
40
|
+
sourceDomain: number
|
|
41
|
+
/** CCTP burn eventNonce */
|
|
42
|
+
nonce: number
|
|
43
|
+
/** burn txHash, same as CCIP request */
|
|
44
|
+
txHash: string
|
|
45
|
+
},
|
|
40
46
|
networkType: NetworkType,
|
|
41
|
-
): Promise<string> {
|
|
42
|
-
const
|
|
43
|
-
|
|
47
|
+
): Promise<{ attestation: string; message: string }> {
|
|
48
|
+
const { sourceDomain, nonce, txHash } = opts
|
|
44
49
|
const circleApiBaseUrl =
|
|
45
50
|
networkType === NetworkType.Mainnet ? CIRCLE_API_URL.mainnet : CIRCLE_API_URL.testnet
|
|
46
|
-
const res = await fetch(
|
|
51
|
+
const res = await fetch(
|
|
52
|
+
`${circleApiBaseUrl}/v2/messages/${sourceDomain}?transactionHash=${txHash}`,
|
|
53
|
+
)
|
|
47
54
|
const json = (await res.json()) as CctpAttestationResponse
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
let att
|
|
56
|
+
if ('messages' in json) {
|
|
57
|
+
att = json.messages.find((m) => m.status === 'complete' && m.eventNonce === nonce.toString())
|
|
50
58
|
}
|
|
51
|
-
|
|
59
|
+
if (!att?.message) throw new CCIPUsdcAttestationError(txHash, json, { context: opts })
|
|
60
|
+
return att
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const LOMBARD_API_URL = {
|
|
64
|
+
mainnet: 'https://mainnet.prod.lombard.finance',
|
|
65
|
+
testnet: 'https://gastald-testnet.prod.lombard.finance',
|
|
52
66
|
}
|
|
53
67
|
|
|
68
|
+
type LombardAttestation =
|
|
69
|
+
| {
|
|
70
|
+
status: 'NOTARIZATION_STATUS_SESSION_APPROVED'
|
|
71
|
+
message_hash: string
|
|
72
|
+
attestation: string
|
|
73
|
+
}
|
|
74
|
+
| { status: string; message_hash: string }
|
|
75
|
+
type LombardAttestationsResponse = { attestations: Array<LombardAttestation> }
|
|
76
|
+
|
|
54
77
|
/**
|
|
55
78
|
* Returns the LBTC attestation for a given payload hash
|
|
56
79
|
*
|
|
@@ -86,3 +109,77 @@ export async function getLbtcAttestation(
|
|
|
86
109
|
}
|
|
87
110
|
return attestation
|
|
88
111
|
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Fetch CCIPv1 offchain token data for USDC and LBTC tokenAmounts
|
|
115
|
+
* @param request - CCIPRequest containing tx.hash and message
|
|
116
|
+
* @returns Promise resolving to an OffchainTokenData for each tokenAmount
|
|
117
|
+
*/
|
|
118
|
+
export async function getOffchainTokenData(
|
|
119
|
+
request: PickDeep<CCIPRequest, 'tx.hash' | `message`>,
|
|
120
|
+
{ logger = console }: WithLogger = {},
|
|
121
|
+
): Promise<OffchainTokenData[]> {
|
|
122
|
+
const { networkType } = networkInfo(request.message.sourceChainSelector)
|
|
123
|
+
|
|
124
|
+
function looksUsdcData(extraData: BytesLike) {
|
|
125
|
+
if (dataLength(extraData) !== 64) return
|
|
126
|
+
// USDCTokenPool's extraData is a packed `SourceTokenDataPayloadV1{uint64 nonce, uint32 sourceDomain}`,
|
|
127
|
+
// which we need to query CCTPv2 (by sourceDomain and txHash) and to filter by nonce among messages,
|
|
128
|
+
// if more than one in tx
|
|
129
|
+
let nonce, sourceDomain
|
|
130
|
+
try {
|
|
131
|
+
// those toNumber conversions throw early in case the bytearray don't look like small numbers
|
|
132
|
+
nonce = toNumber(dataSlice(extraData, 0, 32))
|
|
133
|
+
sourceDomain = toNumber(dataSlice(extraData, 32, 32 + 32))
|
|
134
|
+
return { nonce, sourceDomain } // maybe USDC
|
|
135
|
+
} catch {
|
|
136
|
+
// not USDC
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function looksLbtcData(extraData: BytesLike) {
|
|
141
|
+
// LBTC returns `message_hash`/`payloadHash` directly as `bytes32 extraData`
|
|
142
|
+
if (
|
|
143
|
+
dataLength(extraData) === 32 &&
|
|
144
|
+
getBytes(extraData, 'extraData').filter(Boolean).length > 20 // looks like a hash
|
|
145
|
+
)
|
|
146
|
+
return true
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return Promise.all(
|
|
150
|
+
request.message.tokenAmounts.map(async (tokenAmount, i) => {
|
|
151
|
+
let extraData
|
|
152
|
+
if ('extraData' in tokenAmount) {
|
|
153
|
+
extraData = tokenAmount.extraData
|
|
154
|
+
} else if ('sourceTokenData' in request.message) {
|
|
155
|
+
// v1.2..v1.5
|
|
156
|
+
if (dataLength(request.message.sourceTokenData[i]!) === 64) {
|
|
157
|
+
extraData = request.message.sourceTokenData[i]
|
|
158
|
+
} else {
|
|
159
|
+
;({ extraData } = parseSourceTokenData(request.message.sourceTokenData[i]!))
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (!extraData) return
|
|
163
|
+
const usdcOpts = looksUsdcData(extraData)
|
|
164
|
+
if (usdcOpts) {
|
|
165
|
+
try {
|
|
166
|
+
const usdcAttestation = await getUsdcAttestation(
|
|
167
|
+
{ ...usdcOpts, txHash: request.tx.hash },
|
|
168
|
+
networkType,
|
|
169
|
+
)
|
|
170
|
+
return { _tag: 'usdc', extraData, ...usdcAttestation }
|
|
171
|
+
} catch (err) {
|
|
172
|
+
// maybe not a USDC transfer, or not ready
|
|
173
|
+
logger.warn(`❌ CCTP: Failed to fetch attestation for message:`, request.message, err)
|
|
174
|
+
}
|
|
175
|
+
} else if (looksLbtcData(extraData)) {
|
|
176
|
+
try {
|
|
177
|
+
const lbtcAttestation = await getLbtcAttestation(extraData, networkType)
|
|
178
|
+
return { _tag: 'lbtc', extraData, ...lbtcAttestation }
|
|
179
|
+
} catch (err) {
|
|
180
|
+
logger.warn(`❌ LBTC: Failed to fetch attestation for message:`, extraData, err)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}),
|
|
184
|
+
)
|
|
185
|
+
}
|
package/src/requests.ts
CHANGED
|
@@ -19,8 +19,8 @@ import {
|
|
|
19
19
|
type CCIPMessage,
|
|
20
20
|
type CCIPRequest,
|
|
21
21
|
type CCIPVersion,
|
|
22
|
+
type ChainLog,
|
|
22
23
|
type ChainTransaction,
|
|
23
|
-
type Log_,
|
|
24
24
|
type MessageInput,
|
|
25
25
|
ChainFamily,
|
|
26
26
|
} from './types.ts'
|
|
@@ -78,7 +78,6 @@ function decodeJsonMessage(data: Record<string, unknown> | undefined) {
|
|
|
78
78
|
data_.sourceNetworkInfo?.chainSelector
|
|
79
79
|
if (!sourceChainSelector) throw new CCIPMessageInvalidError(data)
|
|
80
80
|
data_.sourceChainSelector ??= sourceChainSelector
|
|
81
|
-
data_.nonce ??= 0n
|
|
82
81
|
const sourceFamily = networkInfo(sourceChainSelector).family
|
|
83
82
|
|
|
84
83
|
const destChainSelector =
|
|
@@ -181,7 +180,7 @@ export function decodeMessage(data: string | Uint8Array | Record<string, unknown
|
|
|
181
180
|
// try bytearray decoding on each supported chain
|
|
182
181
|
for (const chain of Object.values(supportedChains)) {
|
|
183
182
|
try {
|
|
184
|
-
const decoded = chain.decodeMessage({ data } as unknown as
|
|
183
|
+
const decoded = chain.decodeMessage({ data } as unknown as ChainLog)
|
|
185
184
|
if (decoded) return decoded
|
|
186
185
|
} catch (_) {
|
|
187
186
|
// continue
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// .then((res) => res.text())
|
|
4
4
|
// .then((text) => text.trim())
|
|
5
5
|
export type BaseTokenPool = {
|
|
6
|
-
version: '1.6.
|
|
6
|
+
version: '1.6.1'
|
|
7
7
|
name: 'base_token_pool'
|
|
8
8
|
instructions: []
|
|
9
9
|
types: [
|
|
@@ -868,7 +868,7 @@ export type BaseTokenPool = {
|
|
|
868
868
|
}
|
|
869
869
|
|
|
870
870
|
export const IDL: BaseTokenPool = {
|
|
871
|
-
version: '1.6.
|
|
871
|
+
version: '1.6.1',
|
|
872
872
|
name: 'base_token_pool',
|
|
873
873
|
instructions: [],
|
|
874
874
|
types: [
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// .then((res) => res.text())
|
|
4
4
|
// .then((text) => text.trim())
|
|
5
5
|
export type BurnmintTokenPool = {
|
|
6
|
-
version: '1.6.
|
|
6
|
+
version: '1.6.1'
|
|
7
7
|
name: 'burnmint_token_pool'
|
|
8
8
|
instructions: [
|
|
9
9
|
{
|
|
@@ -951,7 +951,7 @@ export type BurnmintTokenPool = {
|
|
|
951
951
|
}
|
|
952
952
|
|
|
953
953
|
export const IDL: BurnmintTokenPool = {
|
|
954
|
-
version: '1.6.
|
|
954
|
+
version: '1.6.1',
|
|
955
955
|
name: 'burnmint_token_pool',
|
|
956
956
|
instructions: [
|
|
957
957
|
{
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// .then((res) => res.text())
|
|
4
4
|
// .then((text) => text.trim())
|
|
5
5
|
export type CctpTokenPool = {
|
|
6
|
-
version: '1.6.
|
|
6
|
+
version: '1.6.1'
|
|
7
7
|
name: 'cctp_token_pool'
|
|
8
8
|
instructions: [
|
|
9
9
|
{
|
|
@@ -1376,7 +1376,7 @@ export type CctpTokenPool = {
|
|
|
1376
1376
|
}
|
|
1377
1377
|
|
|
1378
1378
|
export const IDL: CctpTokenPool = {
|
|
1379
|
-
version: '1.6.
|
|
1379
|
+
version: '1.6.1',
|
|
1380
1380
|
name: 'cctp_token_pool',
|
|
1381
1381
|
instructions: [
|
|
1382
1382
|
{
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// .then((res) => res.text())
|
|
4
4
|
// .then((text) => text.trim())
|
|
5
5
|
export type CcipCommon = {
|
|
6
|
-
version: '
|
|
6
|
+
version: '1.6.1'
|
|
7
7
|
name: 'ccip_common'
|
|
8
8
|
instructions: []
|
|
9
9
|
accounts: [
|
|
@@ -102,11 +102,26 @@ export type CcipCommon = {
|
|
|
102
102
|
name: 'InvalidSVMAddress'
|
|
103
103
|
msg: 'Invalid SVM address'
|
|
104
104
|
},
|
|
105
|
+
{
|
|
106
|
+
code: 10011
|
|
107
|
+
name: 'InvalidTVMAddress'
|
|
108
|
+
msg: 'Invalid TVM address'
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
code: 10012
|
|
112
|
+
name: 'InvalidAptosAddress'
|
|
113
|
+
msg: 'Invalid Aptos address'
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
code: 10013
|
|
117
|
+
name: 'InvalidSuiAddress'
|
|
118
|
+
msg: 'Invalid Sui address'
|
|
119
|
+
},
|
|
105
120
|
]
|
|
106
121
|
}
|
|
107
122
|
|
|
108
123
|
export const IDL: CcipCommon = {
|
|
109
|
-
version: '
|
|
124
|
+
version: '1.6.1',
|
|
110
125
|
name: 'ccip_common',
|
|
111
126
|
instructions: [],
|
|
112
127
|
accounts: [
|
|
@@ -205,6 +220,21 @@ export const IDL: CcipCommon = {
|
|
|
205
220
|
name: 'InvalidSVMAddress',
|
|
206
221
|
msg: 'Invalid SVM address',
|
|
207
222
|
},
|
|
223
|
+
{
|
|
224
|
+
code: 10011,
|
|
225
|
+
name: 'InvalidTVMAddress',
|
|
226
|
+
msg: 'Invalid TVM address',
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
code: 10012,
|
|
230
|
+
name: 'InvalidAptosAddress',
|
|
231
|
+
msg: 'Invalid Aptos address',
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
code: 10013,
|
|
235
|
+
name: 'InvalidSuiAddress',
|
|
236
|
+
msg: 'Invalid Sui address',
|
|
237
|
+
},
|
|
208
238
|
],
|
|
209
239
|
}
|
|
210
240
|
// generate:end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// .then((res) => res.text())
|
|
4
4
|
// .then((text) => text.trim())
|
|
5
5
|
export type CcipOfframp = {
|
|
6
|
-
version: '
|
|
6
|
+
version: '1.6.1'
|
|
7
7
|
name: 'ccip_offramp'
|
|
8
8
|
constants: [
|
|
9
9
|
{
|
|
@@ -2748,7 +2748,7 @@ export type CcipOfframp = {
|
|
|
2748
2748
|
}
|
|
2749
2749
|
|
|
2750
2750
|
export const IDL: CcipOfframp = {
|
|
2751
|
-
version: '
|
|
2751
|
+
version: '1.6.1',
|
|
2752
2752
|
name: 'ccip_offramp',
|
|
2753
2753
|
constants: [
|
|
2754
2754
|
{
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// else throw new Error('isMut is already true, no need for this workaround anymore');
|
|
9
9
|
// }))
|
|
10
10
|
export type CcipRouter = {
|
|
11
|
-
version: '
|
|
11
|
+
version: '1.6.1'
|
|
12
12
|
name: 'ccip_router'
|
|
13
13
|
docs: [
|
|
14
14
|
'The `ccip_router` module contains the implementation of the Cross-Chain Interoperability Protocol (CCIP) Router.',
|
|
@@ -2339,7 +2339,7 @@ export type CcipRouter = {
|
|
|
2339
2339
|
}
|
|
2340
2340
|
|
|
2341
2341
|
export const IDL: CcipRouter = {
|
|
2342
|
-
version: '
|
|
2342
|
+
version: '1.6.1',
|
|
2343
2343
|
name: 'ccip_router',
|
|
2344
2344
|
docs: [
|
|
2345
2345
|
'The `ccip_router` module contains the implementation of the Cross-Chain Interoperability Protocol (CCIP) Router.',
|
package/src/solana/index.ts
CHANGED
|
@@ -77,15 +77,14 @@ import {
|
|
|
77
77
|
type CCIPMessage,
|
|
78
78
|
type CCIPRequest,
|
|
79
79
|
type CCIPVerifications,
|
|
80
|
+
type ChainLog,
|
|
80
81
|
type ChainTransaction,
|
|
81
82
|
type CommitReport,
|
|
82
83
|
type ExecutionInput,
|
|
83
84
|
type ExecutionReceipt,
|
|
84
85
|
type Lane,
|
|
85
|
-
type Log_,
|
|
86
86
|
type MergeArrayElements,
|
|
87
87
|
type NetworkInfo,
|
|
88
|
-
type OffchainTokenData,
|
|
89
88
|
type WithLogger,
|
|
90
89
|
CCIPVersion,
|
|
91
90
|
ChainFamily,
|
|
@@ -113,7 +112,6 @@ import { IDL as CCIP_CCTP_TOKEN_POOL } from './idl/1.6.0/CCIP_CCTP_TOKEN_POOL.ts
|
|
|
113
112
|
import { IDL as CCIP_OFFRAMP_IDL } from './idl/1.6.0/CCIP_OFFRAMP.ts'
|
|
114
113
|
import { IDL as CCIP_ROUTER_IDL } from './idl/1.6.0/CCIP_ROUTER.ts'
|
|
115
114
|
import { getTransactionsForAddress } from './logs.ts'
|
|
116
|
-
import { fetchSolanaOffchainTokenData } from './offchain.ts'
|
|
117
115
|
import { generateUnsignedCcipSend, getFee } from './send.ts'
|
|
118
116
|
import { type CCIPMessage_V1_6_Solana, type UnsignedSolanaTx, isWallet } from './types.ts'
|
|
119
117
|
import {
|
|
@@ -160,7 +158,7 @@ const unknownTokens: { [mint: string]: string } = {
|
|
|
160
158
|
}
|
|
161
159
|
|
|
162
160
|
/** Solana-specific log structure with transaction reference and log level. */
|
|
163
|
-
export type SolanaLog =
|
|
161
|
+
export type SolanaLog = ChainLog & { tx: SolanaTransaction; data: string; level: number }
|
|
164
162
|
/** Solana-specific transaction structure with versioned transaction response. */
|
|
165
163
|
export type SolanaTransaction = MergeArrayElements<
|
|
166
164
|
ChainTransaction,
|
|
@@ -430,13 +428,13 @@ export class SolanaChain extends Chain<typeof ChainFamily.Solana> {
|
|
|
430
428
|
* - `watch`: Watch for new logs
|
|
431
429
|
* - `programs`: Special option to allow querying by address of interest, but yielding matching
|
|
432
430
|
* logs from specific (string address) program or any (true)
|
|
433
|
-
* @returns AsyncIterableIterator of parsed
|
|
431
|
+
* @returns AsyncIterableIterator of parsed ChainLog objects.
|
|
434
432
|
* @throws {@link CCIPLogsAddressRequiredError} if address is not provided
|
|
435
433
|
* @throws {@link CCIPTopicsInvalidError} if topics contain invalid values
|
|
436
434
|
*/
|
|
437
435
|
async *getLogs(
|
|
438
436
|
opts: LogFilter & { programs?: string[] | true },
|
|
439
|
-
): AsyncGenerator<
|
|
437
|
+
): AsyncGenerator<ChainLog & { tx: SolanaTransaction }> {
|
|
440
438
|
let programs: true | string[]
|
|
441
439
|
if (!opts.address) {
|
|
442
440
|
throw new CCIPLogsAddressRequiredError()
|
|
@@ -895,7 +893,7 @@ export class SolanaChain extends Chain<typeof ChainFamily.Solana> {
|
|
|
895
893
|
* @throws {@link CCIPLogDataMissingError} if log data is missing
|
|
896
894
|
*/
|
|
897
895
|
static decodeCommits(
|
|
898
|
-
log: Pick<
|
|
896
|
+
log: Pick<ChainLog, 'data'>,
|
|
899
897
|
lane?: Omit<Lane, 'destChainSelector'>,
|
|
900
898
|
): CommitReport[] | undefined {
|
|
901
899
|
// Check if this is a CommitReportAccepted event by looking at the discriminant
|
|
@@ -950,7 +948,7 @@ export class SolanaChain extends Chain<typeof ChainFamily.Solana> {
|
|
|
950
948
|
* @throws {@link CCIPLogDataMissingError} if log data is missing
|
|
951
949
|
* @throws {@link CCIPExecutionStateInvalidError} if execution state is invalid
|
|
952
950
|
*/
|
|
953
|
-
static decodeReceipt(log: Pick<
|
|
951
|
+
static decodeReceipt(log: Pick<ChainLog, 'data' | 'tx' | 'index'>): ExecutionReceipt | undefined {
|
|
954
952
|
// Check if this is a ExecutionStateChanged event by looking at the discriminant
|
|
955
953
|
if (!log.data || typeof log.data !== 'string') {
|
|
956
954
|
throw new CCIPLogDataMissingError()
|
|
@@ -983,7 +981,7 @@ export class SolanaChain extends Chain<typeof ChainFamily.Solana> {
|
|
|
983
981
|
} else throw new CCIPExecutionStateInvalidError(util.inspect(decoded.data.state))
|
|
984
982
|
|
|
985
983
|
let returnData
|
|
986
|
-
if (log.tx) {
|
|
984
|
+
if (log.tx?.logs) {
|
|
987
985
|
// use only last receipt per tx+message (i.e. skip intermediary InProgress=1 states for Solana)
|
|
988
986
|
const laterReceiptLog = log.tx.logs
|
|
989
987
|
.filter((l) => l.index > log.index)
|
|
@@ -1107,11 +1105,6 @@ export class SolanaChain extends Chain<typeof ChainFamily.Solana> {
|
|
|
1107
1105
|
return (await this.getMessagesInTx(await this.getTransaction(hash)))[0]!
|
|
1108
1106
|
}
|
|
1109
1107
|
|
|
1110
|
-
/** {@inheritDoc Chain.getOffchainTokenData} */
|
|
1111
|
-
async getOffchainTokenData(request: CCIPRequest): Promise<OffchainTokenData[]> {
|
|
1112
|
-
return fetchSolanaOffchainTokenData(request, this)
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
1108
|
/**
|
|
1116
1109
|
* {@inheritDoc Chain.generateUnsignedExecute}
|
|
1117
1110
|
* @returns instructions - array of instructions to execute the report
|
|
@@ -1209,13 +1202,13 @@ export class SolanaChain extends Chain<typeof ChainFamily.Solana> {
|
|
|
1209
1202
|
if (Array.isArray(data)) {
|
|
1210
1203
|
if (data.every((e) => typeof e === 'string')) return getErrorFromLogs(data)
|
|
1211
1204
|
else if (data.every((e) => typeof e === 'object' && 'data' in e && 'address' in e))
|
|
1212
|
-
return getErrorFromLogs(data as
|
|
1205
|
+
return getErrorFromLogs(data as ChainLog[])
|
|
1213
1206
|
} else if (typeof data === 'object') {
|
|
1214
1207
|
if ('transactionLogs' in data && 'transactionMessage' in data) {
|
|
1215
|
-
const parsed = getErrorFromLogs(data.transactionLogs as
|
|
1208
|
+
const parsed = getErrorFromLogs(data.transactionLogs as ChainLog[] | string[])
|
|
1216
1209
|
if (parsed) return { message: data.transactionMessage, ...parsed }
|
|
1217
1210
|
}
|
|
1218
|
-
if ('logs' in data) return getErrorFromLogs(data.logs as
|
|
1211
|
+
if ('logs' in data) return getErrorFromLogs(data.logs as ChainLog[] | string[])
|
|
1219
1212
|
} else if (typeof data === 'string') {
|
|
1220
1213
|
const parsedExtraArgs = this.decodeExtraArgs(getDataBytes(data))
|
|
1221
1214
|
if (parsedExtraArgs) return parsedExtraArgs
|
package/src/solana/offchain.ts
CHANGED
|
@@ -1,29 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { PublicKey } from '@solana/web3.js'
|
|
1
|
+
import { BorshCoder } from '@coral-xyz/anchor'
|
|
3
2
|
import { hexlify } from 'ethers'
|
|
4
3
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
CCIPCctpMultipleEventsError,
|
|
8
|
-
CCIPDataFormatUnsupportedError,
|
|
9
|
-
} from '../errors/index.ts'
|
|
10
|
-
import { getUsdcAttestation } from '../offchain.ts'
|
|
11
|
-
import type { CCIPMessage, CCIPRequest, OffchainTokenData, WithLogger } from '../types.ts'
|
|
12
|
-
import { bytesToBuffer, networkInfo, util } from '../utils.ts'
|
|
4
|
+
import type { OffchainTokenData } from '../types.ts'
|
|
5
|
+
import { bytesToBuffer } from '../utils.ts'
|
|
13
6
|
import { IDL as BASE_TOKEN_POOL } from './idl/1.6.0/BASE_TOKEN_POOL.ts'
|
|
14
7
|
import { IDL as CCTP_TOKEN_POOL } from './idl/1.6.0/CCIP_CCTP_TOKEN_POOL.ts'
|
|
15
|
-
import type { SolanaLog, SolanaTransaction } from './index.ts'
|
|
16
|
-
import { hexDiscriminator } from './utils.ts'
|
|
17
|
-
|
|
18
|
-
interface CcipCctpMessageSentEvent {
|
|
19
|
-
originalSender: PublicKey
|
|
20
|
-
remoteChainSelector: BN
|
|
21
|
-
msgTotalNonce: BN
|
|
22
|
-
eventAddress: PublicKey
|
|
23
|
-
sourceDomain: number
|
|
24
|
-
cctpNonce: BN
|
|
25
|
-
messageSentBytes: Uint8Array
|
|
26
|
-
}
|
|
27
8
|
|
|
28
9
|
interface CcipCctpMessageAndAttestation {
|
|
29
10
|
message: {
|
|
@@ -38,84 +19,6 @@ const cctpTokenPoolCoder = new BorshCoder({
|
|
|
38
19
|
errors: [...BASE_TOKEN_POOL.errors, ...CCTP_TOKEN_POOL.errors],
|
|
39
20
|
})
|
|
40
21
|
|
|
41
|
-
/**
|
|
42
|
-
* Analyzes a Solana transaction to extract CcipCctpMessageSentEvent, fetch Circle attestation,
|
|
43
|
-
* and encode the data in the format required by the destination chain.
|
|
44
|
-
* @param request - CCIP request containing transaction data and chain routing info.
|
|
45
|
-
* @param logger - Logger instance for logging messages.
|
|
46
|
-
* @returns Array of encoded offchain token data (only one supported for Solana right now).
|
|
47
|
-
* @throws Error if transaction hash is missing or CcipCctpMessageSentEvent parsing fails.
|
|
48
|
-
*/
|
|
49
|
-
export async function fetchSolanaOffchainTokenData(
|
|
50
|
-
request: Pick<CCIPRequest, 'tx' | 'lane'> & {
|
|
51
|
-
message: CCIPMessage
|
|
52
|
-
log: Pick<CCIPRequest['log'], 'topics' | 'index' | 'transactionHash' | 'address'>
|
|
53
|
-
},
|
|
54
|
-
{ logger = console }: WithLogger = {},
|
|
55
|
-
): Promise<OffchainTokenData[]> {
|
|
56
|
-
if (!request.message.tokenAmounts.length) {
|
|
57
|
-
return []
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (request.message.tokenAmounts.length > 1) {
|
|
61
|
-
throw new CCIPDataFormatUnsupportedError(
|
|
62
|
-
`Expected at most 1 token transfer, found ${request.message.tokenAmounts.length}`,
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const { networkType } = networkInfo(request.lane.sourceChainSelector)
|
|
67
|
-
const txSignature = request.log.transactionHash
|
|
68
|
-
|
|
69
|
-
// Parse Solana transaction to find CCTP event
|
|
70
|
-
const tx = request.tx as SolanaTransaction
|
|
71
|
-
const log = request.log as SolanaLog
|
|
72
|
-
const logMessages = tx.tx.meta!.logMessages!
|
|
73
|
-
// there may have multiple ccipSend calls in same tx;
|
|
74
|
-
// use `invoke [level]` to filter only logs inside this call
|
|
75
|
-
const requestInvokeIdx = logMessages.findLastIndex(
|
|
76
|
-
(l, i) => i < log.index && l === `Program ${request.log.address} invoke [${log.level}]`,
|
|
77
|
-
)
|
|
78
|
-
const cctpEvents = []
|
|
79
|
-
for (const l of tx.logs) {
|
|
80
|
-
if (requestInvokeIdx >= l.index || l.index >= log.index) continue
|
|
81
|
-
if (l.topics[0] !== hexDiscriminator('CcipCctpMessageSentEvent')) continue
|
|
82
|
-
const decoded = cctpTokenPoolCoder.events.decode(l.data)
|
|
83
|
-
if (!decoded) throw new CCIPCctpDecodeError(util.inspect(l))
|
|
84
|
-
cctpEvents.push(decoded.data as unknown as CcipCctpMessageSentEvent)
|
|
85
|
-
}
|
|
86
|
-
const offchainTokenData: OffchainTokenData[] = request.message.tokenAmounts.map(() => undefined)
|
|
87
|
-
|
|
88
|
-
// If no CcipCctpMessageSentEvent found, return defaults so we don't block execution
|
|
89
|
-
if (cctpEvents.length === 0) {
|
|
90
|
-
logger.debug('No USDC/CCTP events found')
|
|
91
|
-
return offchainTokenData
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Currently, we only support ONE token per transfer
|
|
95
|
-
if (cctpEvents.length > 1) {
|
|
96
|
-
throw new CCIPCctpMultipleEventsError(cctpEvents.length, txSignature)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// NOTE: assuming USDC token is the first (and only) token in the CCIP message, we will process the CCTP event.
|
|
100
|
-
// If later multi-token transfers support is added, we need to add more info in order to match each token with it's event and offchainTokenData.
|
|
101
|
-
const cctpEvent = cctpEvents[0]
|
|
102
|
-
if (cctpEvent) {
|
|
103
|
-
const message = hexlify(cctpEvent.messageSentBytes)
|
|
104
|
-
try {
|
|
105
|
-
// Extract message bytes to fetch circle's attestation and then encode offchainTokenData.
|
|
106
|
-
const attestation = await getUsdcAttestation(message, networkType)
|
|
107
|
-
|
|
108
|
-
offchainTokenData[0] = { _tag: 'usdc', message, attestation }
|
|
109
|
-
} catch (error) {
|
|
110
|
-
logger.warn(`❌ Solana CCTP: Failed to fetch attestation for ${txSignature}:`, message, error)
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
logger.debug('Got Solana offchain token data', offchainTokenData)
|
|
115
|
-
|
|
116
|
-
return offchainTokenData
|
|
117
|
-
}
|
|
118
|
-
|
|
119
22
|
/**
|
|
120
23
|
* Encodes CCTP message and attestation
|
|
121
24
|
*
|
package/src/solana/utils.ts
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
CCIPTokenMintNotFoundError,
|
|
27
27
|
CCIPTransactionNotFinalizedError,
|
|
28
28
|
} from '../errors/index.ts'
|
|
29
|
-
import type {
|
|
29
|
+
import type { ChainLog, WithLogger } from '../types.ts'
|
|
30
30
|
import { getDataBytes, sleep } from '../utils.ts'
|
|
31
31
|
import type { IDL as BASE_TOKEN_POOL_IDL } from './idl/1.6.0/BASE_TOKEN_POOL.ts'
|
|
32
32
|
import type { UnsignedSolanaTx, Wallet } from './types.ts'
|
|
@@ -143,7 +143,7 @@ export function camelToSnakeCase(str: string): string {
|
|
|
143
143
|
.replace(/^_/, '')
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
type ParsedLog = Pick<
|
|
146
|
+
type ParsedLog = Pick<ChainLog, 'topics' | 'index' | 'address' | 'data'> & {
|
|
147
147
|
data: string
|
|
148
148
|
level: number
|
|
149
149
|
}
|
|
@@ -160,7 +160,7 @@ type ParsedLog = Pick<Log_, 'topics' | 'index' | 'address' | 'data'> & {
|
|
|
160
160
|
* This function:
|
|
161
161
|
* 1. Tracks the program call stack to determine which program emitted each log
|
|
162
162
|
* 2. Extracts the first 8 bytes from base64 "Program data:" logs as topics (event discriminants)
|
|
163
|
-
* 3. Converts logs to EVM-compatible
|
|
163
|
+
* 3. Converts logs to EVM-compatible ChainLog format for CCIP compatibility
|
|
164
164
|
* 4. Returns ALL logs from the transaction - filtering should be done by the caller
|
|
165
165
|
*
|
|
166
166
|
* @param logs - Array of logMessages from Solana transaction
|
|
@@ -215,7 +215,10 @@ export function parseSolanaLogs(logs: readonly string[]): ParsedLog[] {
|
|
|
215
215
|
* @returns Parsed error info with program and error details.
|
|
216
216
|
*/
|
|
217
217
|
export function getErrorFromLogs(
|
|
218
|
-
logs_:
|
|
218
|
+
logs_:
|
|
219
|
+
| readonly string[]
|
|
220
|
+
| readonly Pick<ChainLog, 'address' | 'index' | 'data' | 'topics'>[]
|
|
221
|
+
| null,
|
|
219
222
|
): { program: string; [k: string]: string } | undefined {
|
|
220
223
|
if (!logs_?.length) return
|
|
221
224
|
let logs
|
|
@@ -229,7 +232,7 @@ export function getErrorFromLogs(
|
|
|
229
232
|
(acc, l) =>
|
|
230
233
|
// if acc is empty (i.e. on last log), or it is emitted by the same program and not a Program data:
|
|
231
234
|
!acc.length || (l.address === acc[0]!.address && !l.topics.length) ? [l, ...acc] : acc,
|
|
232
|
-
[] as Pick<
|
|
235
|
+
[] as Pick<ChainLog, 'address' | 'index' | 'data'>[],
|
|
233
236
|
)
|
|
234
237
|
.map(({ data }) => data as string)
|
|
235
238
|
.reduceRight(
|