Skip to main content
이 페이지는 AI 자동 번역입니다. 모든 내용은 영문판을 기준으로 합니다.영문판 보기 →
PDA(프로그램 파생 주소)와 CPI(크로스 프로그램 호출)는 Raydium을 가능하게 하는 두 가지 기본 요소입니다. PDA를 사용하면 프로그램이 개인 키 없이 결정론적 주소를 “소유”할 수 있습니다. 이것이 풀 권한자와 볼트가 작동하는 방식입니다. CPI를 사용하면 한 프로그램이 다른 프로그램을 호출할 수 있습니다. 이것이 Raydium이 SPL Token 프로그램을 통해 토큰을 스왑하고 통합자가 Raydium을 자신의 흐름에 조합하는 방식입니다. Raydium의 소스 코드를 읽기 전에 둘 다 이해할 가치가 있습니다.

PDA: 키 없는 주소

프로그램 파생 주소(Program-Derived Address)는 다음과 같은 공개 키입니다:
  • ed25519 곡선 위에 있지 않습니다 (개인 키가 존재하지 않음).
  • 프로그램 ID와 시드 집합에서 결정론적으로 파생됩니다.
  • invoke_signed를 통해 오직 파생 프로그램만 서명할 수 있습니다.
모든 Raydium 풀 권한자, 모든 풀 상태 계정, 모든 볼트, 모든 팜 상태는 모두 PDA입니다.

파생

PDA는 프로그램 ID를 시드와 함께 해싱하고, 결과를 곡선 밖으로 강제하는 “범프” 바이트를 찾아서 계산됩니다. 곡선 밖의 주소를 생성하는 첫 번째 범프(일반적으로 255에서 시작하여 감소)가 정규 범프(canonical bump)입니다.
시드는 무엇이든 될 수 있습니다. 문자열, 다른 공개 키, 리틀 엔디안 바이트로 인코딩된 u64 값 등입니다. Raydium의 관례는 사람이 읽을 수 있는 접두사 뒤에 고유 식별자를 붙이는 것입니다.

Raydium PDA 패턴

Raydium 프로그램의 일반적인 PDA: 사용자와 통합자는 아무것도 가져올 필요 없이 이러한 PDA를 계산할 수 있습니다. 공개 입력(풀 ID, 팜 ID, 사용자 키)이 주어지면 PDA는 결정론적입니다.

정규 범프

원칙적으로 곡선 밖의 주소를 생성하는 여러 범프가 있을 수 있지만, Raydium의 프로그램은 항상 정규 범프(255에서 감소하여 찾음)를 사용합니다. 이는 PDA의 계정 데이터에 저장되므로 후속 트랜잭션이 이를 전달하고 (비용이 많이 드는) 파생 루프를 건너뛸 수 있습니다:
(CLMM의 PoolState는 대신 bump: [u8; 1]을 저장하므로, 한 가지 형태를 가정하기보다는 특정 프로그램의 구조를 확인하세요.) 후속 트랜잭션에서 범프는 풀 상태에서 읽혀지며 재계산되지 않습니다.

CPI: 다른 프로그램 호출

크로스 프로그램 호출(Cross-Program Invocation)을 사용하면 프로그램이 단일 트랜잭션 내에서 다른 프로그램의 명령을 인라인으로 호출할 수 있습니다. Raydium은 CPI를 광범위하게 사용합니다:
  • 스왑 명령은 SPL Token 프로그램을 호출하여 토큰을 이동합니다.
  • CLMM은 Metaplex를 호출하여 포지션 NFT를 민트합니다.
  • 풀 생성은 System Program을 호출하여 계정을 할당합니다.
  • Farm v6은 SPL Token을 호출하여 보상을 전송합니다.
통합자도 CPI를 사용하여 Raydium 으로 호출합니다. 이것이 볼트 전략, 레버리지 LP 프로토콜, 자동 복합 프로토콜이 작동하는 방식입니다. integration-guides/cpi-integration을 참조하세요.

invoke와 invoke_signed

Solana 런타임은 두 가지 CPI 기본 요소를 제공합니다:
  • invoke: 다른 프로그램을 호출합니다. 호출된 프로그램은 외부 트랜잭션의 서명자를 상속합니다.
  • invoke_signed: PDA를 대신하여 다른 프로그램을 호출합니다. 런타임이 PDA의 시드를 검증하고 서명을 승인합니다.
invoke_signed는 프로그램이 개인 키를 관리하지 않고도 계정에 대한 권한을 보유할 수 있게 하는 마법입니다.

예제: Raydium이 풀 볼트에서 전송

풀 볼트는 권한이 풀 프로그램의 PDA인 Token Account입니다. 스왑 중에 토큰을 전송하려면 풀 프로그램이 해당 PDA로 서명해야 합니다:
런타임은 invoke_signed가 CPMM 프로그램에 의해 호출되는 것을 보고, vault_and_lp_mint_auth_seed + bump이 CPMM 프로그램 ID로 해싱될 때 pool_authority의 주소로 파생되는지 검증하고, 토큰 전송에 대한 권한 서명을 허용합니다. 개인 키는 관여하지 않습니다.

예제: 통합자가 Raydium CPMM 호출

통합자 프로그램(예: 에스크로)은 CPI를 통해 Raydium의 swap_base_input을 호출할 수 있습니다:
이것이 정규 통합 패턴입니다. 전체 에스크로 예제는 integration-guides/cpi-integration을 참조하세요.

CPI 깊이 제한

Solana는 CPI 깊이를 4 레벨로 제한합니다. 트랜잭션의 최상위 명령은 깊이 0으로 계산되며, 각 CPI 호출은 깊이를 증가시킵니다. 실제 영향: Raydium의 자체 스왑은 이미 1-2 레벨의 CPI를 사용합니다 (Raydium → SPL Token). 통합자가 Raydium을 호출하면 2입니다. 해당 통합자가 다른 통합자에 의해 호출되면 3입니다. 4번째 레벨이 제한입니다. 대부분의 조합은 이를 쉽게 초과하지 않지만, 깊은 중첩 (애그리게이터 → 라우터 → Raydium → 훅)은 이에 도달할 수 있습니다. 깊게 중첩하기보다는 평평하게 설계하세요.

남은 계정

Raydium 명령이 가변 개수의 계정이 필요할 때 (예: CLMM 스왑이 알 수 없는 개수의 틱 배열을 교차할 때), 추가 계정은 남은 계정으로 전달됩니다. 고정 계정 목록에 추가되고 위치별로 해석됩니다. CPMM의 SwapV2는 전송 훅 프로그램의 추가 필수 계정에 대해 남은 계정을 사용합니다. 클라이언트는 필요한 계정을 가져와 추가합니다:
CPI 레벨에서 통합자는 자신의 명령을 통해 남은 계정을 전달해야 합니다:

PDA 함정

잘못된 시드 → 잘못된 주소

시드가 잘못된 순서, 잘못된 인코딩이거나 추가 바이트를 포함/제외하는 버그는 조용히 다른 PDA를 생성합니다. 트랜잭션은 모호하게 실패합니다 (프로그램이 존재하지 않는 계정을 읽으려고 시도). 항상 시드 파생을 알려진 황금 값에 대해 단위 테스트하세요.

범프를 저장하지 않음

모든 트랜잭션에서 범프를 재파생하면 파생 루프에 대한 계산 비용을 지불합니다. 정규 범프를 PDA의 데이터에 저장하고 거기서 읽으세요.

정규 범프와 비정규 범프 혼동

비정규 범프 (곡선 밖을 생성하는 것을 찾은 경우)는 invoke_signed에서 허용되지만 Raydium의 프로그램에서 assert_eq!(bump, canonical_bump)를 통해 거부됩니다. 누군가 비정규 범프로 PDA를 주장하려고 하면 트랜잭션이 실패합니다.

PDA를 소유 프로그램이 아닐 때 서명자로 전달

PDA의 파생에 있는 프로그램 ID만 해당 시드로 invoke_signed할 수 있습니다. 시도하면 런타임이 거부합니다.

CPI 함정

remaining_accounts 전달 잊음

외부 명령이 remaining_accounts에서 전송 훅 계정을 전달하지만 Raydium으로의 CPI가 이를 전달하지 않으면 Raydium은 훅 계정을 찾을 수 없어 실패합니다. 항상 필요한 CPI에 with_remaining_accounts를 포함하세요.

쓰기 가능 플래그 불일치

외부 명령이 쓰기 가능으로 표시하는 계정도 호출된 프로그램이 쓰려고 의도하는 경우 CPI 호출에서 쓰기 가능해야 합니다. 불일치 → 런타임 거부.

렌트 고려하지 않음

계정을 생성하는 프로그램으로의 CPI (예: ATA 생성)는 지불자가 렌트를 위한 충분한 SOL을 가져야 합니다. 실패한 렌트 검사는 모호한 오류로 나타납니다.

작동 예제: Raydium CPMM PDA 계산

이것은 getPoolInfoFromRpc({ poolId })를 호출할 때 Raydium SDK가 내부적으로 정확히 수행하는 것입니다. 왕복 없이 관련 PDA를 파생시킵니다.

참고 자료

출처: