Fixes on show, send and manualExec for EVM<->SOL#231
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Coverage Report |
There was a problem hiding this comment.
Pull request overview
This PR fixes multiple Solana↔EVM interoperability issues across send, getFee, and manualExec, focusing on better transaction simulation support (V0/ALTs), attestation encoding/lookup normalization, and safer value transformation for API responses.
Changes:
- Switch Solana
getFeeand account derivation paths to V0 simulation (ALT-capable) by simulating.instruction()results instead of using Anchor.view(). - Normalize/extend offchain token data handling: LBTC attestation lookup now normalizes extraData to hex, and Solana offchain encoding adds an LBTC raw-attestation branch.
- Harden utilities: treat empty-string bytes as empty, and prevent
convertKeysToCamelCasefrom passingnull/undefinedintomapValues(with tests).
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| ccip-sdk/src/utils.ts | Handle empty-string BytesLike and make convertKeysToCamelCase null-safe before mapValues. |
| ccip-sdk/src/utils.test.ts | Add regression test ensuring mapValues is not called for null/undefined leaves. |
| ccip-sdk/src/solana/send.ts | Use V0 simulation for getFee and deriveAccountsCcipSend, add lookup-table resolution during derivation. |
| ccip-sdk/src/solana/offchain.ts | Add LBTC branch to encode raw attestation bytes without Borsh wrapping. |
| ccip-sdk/src/solana/offchain.test.ts | Add tests for USDC Borsh encoding and LBTC raw-attestation encoding behavior. |
| ccip-sdk/src/solana/index.ts | Add retry branch intended to clear stale buffers when AlreadyContainsChunk occurs. |
| ccip-sdk/src/solana/exec.ts | Switch execution account derivation to V0 simulation and resolve ALTs during the derivation loop. |
| ccip-sdk/src/offchain.ts | Normalize LBTC extraData to hex before calling the attestation API. |
| ccip-sdk/src/offchain.test.ts | Add tests ensuring LBTC base64/hex extraData both result in hex attestation API requests. |
| ccip-sdk/src/evm/index.ts | Normalize empty calldata strings into valid hex bytes for ethers ABI encoding. |
| ccip-sdk/src/errors/recovery.ts | Add a recovery hint for the new simulation “no return data” error code. |
| ccip-sdk/src/errors/codes.ts | Introduce SOLANA_SIMULATION_NO_RETURN_DATA error code. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (obj == null) return obj | ||
| if (typeof obj !== 'object') return mapValues ? mapValues(obj, key) : obj |
There was a problem hiding this comment.
Why this change? They're equivalent?
Prior to this change, we entered the conditional in either of the branches, but in the former, mapValues is always falsy, which goes directly to the else branch, which is effectively the same action taken by the first if
There was a problem hiding this comment.
Old clause:
if (obj == null || typeof obj !== 'object') return mapValues ? mapValues(obj, key) : obj
When convertKeysToCamelCase({ receiver: null }, mapValues) recurses into the null value:
obj == null → true, short-circuits the ||- But then checks
mapValues ? mapValues(null, key) : null mapValuesis truthy → callsmapValues(null, "receiver")→ crashes
New code returns null directly without ever calling mapValues
See this TS Playground example
There was a problem hiding this comment.
CI was failing here:
test at src/evm/fork.test.ts:168:5
✖ should return native and token balances for CCIP transfer participants (210.837879ms)
TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received null
at Buffer.from (node:buffer:348:9)
at decodeBase64 (file:///home/runner/work/ccip-tools-ts/ccip-tools-ts/node_modules/ethers/lib.esm/utils/base64.js:27:32)
at getAddressBytes (file:///home/runner/work/ccip-tools-ts/ccip-tools-ts/ccip-sdk/src/utils.ts:459:25)
at EVMChain.getAddress (file:///home/runner/work/ccip-tools-ts/ccip-tools-ts/ccip-sdk/src/evm/index.ts:569:18)
at decodeAddress (file:///home/runner/work/ccip-tools-ts/ccip-tools-ts/ccip-sdk/src/utils.ts:307:16)
at file:///home/runner/work/ccip-tools-ts/ccip-tools-ts/ccip-sdk/src/requests.ts:92:11
at convertKeysToCamelCase (file:///home/runner/work/ccip-tools-ts/ccip-tools-ts/ccip-sdk/src/utils.ts:486:66)
at convertKeysToCamelCase (file:///home/runner/work/ccip-tools-ts/ccip-tools-ts/ccip-sdk/src/utils.ts:493:27)
at file:///home/runner/work/ccip-tools-ts/ccip-tools-ts/ccip-sdk/src/utils.ts:483:27
at Array.map (<anonymous>) {
code: 'ERR_INVALID_ARG_TYPE'
}
Fixes for Solana↔EVM across
send,getFeeandmanualExecAttestation lookup: normalize hash encoding
The attestation API expects hex-encoded hashes, but Solana messages encode the token's extra data as base64. The fix normalizes to hex before calling the API so both formats work transparently.
EVM→Solana manual execution fixes
V0 transaction support for account derivation: The derivation loop used legacy transactions (via Anchor's
.view()), which don't support Address Lookup Tables (ALTs). Some TP's requires ALTs, so the legacy tx exceeded Solana's 1232-byte size limit. Switched to V0 transaction simulation which supports ALTs.LBTC attestation encoding : The offchain token data encoder only handled USDC (Borsh-wrapped). Added an LBTC branch that passes the raw attestation bytes directly.
Stale buffer cleanup on retry: If a previous
manualExecfails mid-way, it leaves a partially-filled buffer PDA on-chain, blocking retries. The fix detects this and automatically closes the stale buffer before retrying.SOL→EVM send fixes
Applied the same V0 simulation fix to the send path: both
getFee()and account derivation (deriveAccountsCcipSend) now use V0 transactions instead of legacy, enabling fee estimation and sends for any token pool that uses ALTs.SOL→EVM manual execution fix
Fixed
invalid BytesLike valueerror when executing Solana-sourced messages on EVM. Atlas returns an empty string (not0x) for the data field on messages with no calldata, which broke the ethers ABI encoder. The fix normalizes empty strings to valid hex bytes.Null-safe value transformation in
convertKeysToCamelCaseThe
convertKeysToCamelCaseutility recursively walks API response objects and applies amapValuescallback to transform leaf values (e.g. decoding addresses, casting toBigInt). When the CCIP API returnsnullfor an address field, the null was passed directly to the callback, which calleddecodeAddress(null)→Buffer.from(null)→ crash.The fix short-circuits null/undefined values before they reach the callback, preserving them as-is in the output