@@ -243,10 +243,13 @@ function debugPdaDerivation() {
243243 const messageTransmitterProgramId = new PublicKey ( 'CCTPmbSD7gX1bxKPAmg77w8oFzNFpaQiQUWD43TKaecd' ) ;
244244
245245 // Known working values from successful transaction (nonce 288574)
246- // Key finding: on-chain data shows firstNonce = 288001, NOT 288000!
247- // CCTP uses 1-indexed buckets: firstNonce = floor((nonce-1)/6400)*6400 + 1
248246 const knownUsedNonces = '3ewgRKdMT8WjPjExuVuZ9gZ7qDYwquefpwtL1SUkLCxf' ;
249247 const sourceDomain = 4 ; // Noble
248+ const nonce = 288574n ;
249+
250+ // Correct formula: firstNonce = floor((nonce-1)/6400)*6400 + 1
251+ const NONCES_PER_ACCOUNT = 6400n ;
252+ const firstNonce = ( ( nonce - 1n ) / NONCES_PER_ACCOUNT ) * NONCES_PER_ACCOUNT + 1n ;
250253
251254 const results = [ ] ;
252255
@@ -267,30 +270,35 @@ function debugPdaDerivation() {
267270 console . log ( '=== PDA Derivation Investigation ===' ) ;
268271 console . log ( 'Known working PDA:' , knownUsedNonces ) ;
269272 console . log ( 'Program:' , messageTransmitterProgramId . toString ( ) ) ;
273+ console . log ( 'Nonce:' , nonce . toString ( ) , '→ firstNonce:' , firstNonce . toString ( ) ) ;
270274 console . log ( '' ) ;
271275
272- // Standard LE encoding - with CORRECTED first nonce (288001 not 288000)
273- const seed1 = bytesFromString ( 'used_nonces' ) ;
274- const domainLE = u32ToBytesLE ( sourceDomain ) ;
275-
276- // The BUG was here: using 288000 instead of 288001
277- // CCTP buckets are 1-indexed: firstNonce = floor((nonce-1)/6400)*6400 + 1
278- const wrongNonce = 288000n ; // What we were using
279- const correctNonce = 288001n ; // What CCTP actually uses (from on-chain data)
280-
281276 console . log ( '=== KEY FINDING ===' ) ;
282- console . log ( 'On-chain firstNonce from account data: 288001' ) ;
283- console . log ( 'What we were calculating: 288000' ) ;
284- console . log ( 'CCTP uses 1-indexed buckets!' ) ;
285- console . log ( 'Formula: firstNonce = floor((nonce-1)/6400)*6400 + 1' ) ;
277+ console . log ( 'From CCTP source code (get_nonce_pda.rs):' ) ;
278+ console . log ( 'Seeds use DECIMAL STRINGS, not binary encoding!' ) ;
279+ console . log ( ' - domain "4" (not 0x04000000)' ) ;
280+ console . log ( ' - firstNonce "288001" (not 0x0165040000000000)' ) ;
281+ console . log ( ' - delimiter "" for domains < 11' ) ;
286282 console . log ( '' ) ;
287283
288- console . log ( 'Testing with WRONG nonce (288000):' ) ;
289- tryDerivation ( 'WRONG: [used_nonces, domain_le, 288000_le]' , [ seed1 , domainLE , u64ToBytesLE ( wrongNonce ) ] ) ;
284+ // WRONG: Binary encoding (what we were doing)
285+ console . log ( 'Testing WRONG approach (binary encoding):' ) ;
286+ tryDerivation ( 'WRONG: binary LE' , [
287+ bytesFromString ( 'used_nonces' ) ,
288+ u32ToBytesLE ( sourceDomain ) ,
289+ u64ToBytesLE ( firstNonce )
290+ ] ) ;
290291
291292 console . log ( '' ) ;
292- console . log ( 'Testing with CORRECT nonce (288001):' ) ;
293- tryDerivation ( 'CORRECT: [used_nonces, domain_le, 288001_le]' , [ seed1 , domainLE , u64ToBytesLE ( correctNonce ) ] ) ;
293+ console . log ( 'Testing CORRECT approach (string encoding):' ) ;
294+ // CORRECT: String encoding (from CCTP source)
295+ const delimiter = sourceDomain < 11 ? '' : '-' ;
296+ tryDerivation ( 'CORRECT: strings' , [
297+ bytesFromString ( 'used_nonces' ) ,
298+ bytesFromString ( sourceDomain . toString ( ) ) , // "4"
299+ bytesFromString ( delimiter ) , // ""
300+ bytesFromString ( firstNonce . toString ( ) ) // "288001"
301+ ] ) ;
294302
295303 console . log ( '' ) ;
296304 console . log ( '=== Summary ===' ) ;
@@ -823,19 +831,29 @@ async function relayToSolana() {
823831 const tokenMessengerMinterEventAuthority = new PublicKey ( 'CNfZLeeL4RUxwfPnjA3tLiQt4y43jp4V7bMpga673jf9' ) ;
824832
825833 // ============ DYNAMIC: UsedNonces depends on nonce bucket ============
826- // CCTP uses 1-indexed buckets! Formula: firstNonce = floor((nonce-1)/6400)*6400 + 1
827- // This was discovered by inspecting on-chain account data which showed firstNonce=288001
834+ // CCTP uses 1-indexed buckets: firstNonce = floor((nonce-1)/6400)*6400 + 1
835+ // CRITICAL: Seeds use DECIMAL STRINGS, not binary encoding!
836+ // Source: https://github.com/circlefin/solana-cctp-contracts/blob/master/programs/message-transmitter/src/instructions/get_nonce_pda.rs
828837 const NONCES_PER_ACCOUNT = 6400n ;
829838 const firstNonce = ( ( nonceValue - 1n ) / NONCES_PER_ACCOUNT ) * NONCES_PER_ACCOUNT + 1n ;
830839
831- // Derive usedNonces PDA dynamically (now works correctly with 1-indexed buckets!)
832- const sourceDomainBuffer = u32ToBytesLE ( sourceDomain ) ;
833- const firstNonceBuffer = u64ToBytesLE ( firstNonce ) ;
840+ // Seeds: ["used_nonces", domain_as_string, delimiter, firstNonce_as_string]
841+ // For domains < 11, delimiter is empty
842+ const domainString = sourceDomain . toString ( ) ; // "4" not binary bytes
843+ const firstNonceString = firstNonce . toString ( ) ; // "288001" not binary bytes
844+ const delimiter = sourceDomain < 11 ? '' : '-' ;
845+
834846 const [ usedNonces ] = PublicKey . findProgramAddressSync (
835- [ bytesFromString ( 'used_nonces' ) , sourceDomainBuffer , firstNonceBuffer ] ,
847+ [
848+ bytesFromString ( 'used_nonces' ) ,
849+ bytesFromString ( domainString ) ,
850+ bytesFromString ( delimiter ) ,
851+ bytesFromString ( firstNonceString )
852+ ] ,
836853 messageTransmitterProgramId
837854 ) ;
838855 log ( `Nonce ${ nonceValue } → firstNonce bucket ${ firstNonce } ` , 'info' ) ;
856+ log ( `usedNonces seeds: ["used_nonces", "${ domainString } ", "${ delimiter } ", "${ firstNonceString } "]` , 'info' ) ;
839857 log ( `usedNonces (derived): ${ usedNonces . toString ( ) } ` , 'info' ) ;
840858 log ( `authorityPda: ${ authorityPda . toString ( ) } ` , 'info' ) ;
841859
0 commit comments