Skip to content

fix(lazer): update svm integration guide #565

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 15, 2025
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 115 additions & 23 deletions pages/lazer/integrate-as-consumer/svm.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,34 @@ Integrating with Pyth Lazer in smart contracts as a consumer is a three-step pro

### Use Pyth Lazer SDK into smart contracts

Pyth Lazer provides a [Rust SDK](https://github.com/pyth-network/pyth-crosschain/tree/main/lazer/sdk/rust), which allows consumers to parse the price updates.
Pyth Lazer provides a [Solana SDK](https://docs.rs/pyth-lazer-solana-contract/latest/pyth_lazer_solana_contract/), which allows consumers to parse and verify the price updates on Solana-compatible chains.

Add the following to your `Cargo.toml` file:
To start, add the following to your `Cargo.toml` file:

```toml copy
[dependencies]
pyth-lazer-sdk = 0.1.0
pyth-lazer-solana-contract = { version = "0.2.0", features = ["no-entrypoint"] }
```

Now you can create an instruction or multiple instructions that will receive Pyth Lazer messages.
The instruction data sent to your program should include a byte array containing the Pyth Lazer message. The instruction data can also contain any other parameters your contracts may need.

In order to successfully validate the Pyth Lazer message, the instruction needs to receive the standard Solana sysvar account and Pyth Lazer storage account (`3rdJbqfnagQ4yx9HXJViD4zc4xpiSqmFsKpPuSCQVyQL`). You may also add any other accounts you need.
In order to successfully validate the Pyth Lazer message, the instruction needs to receive the following accounts:

- Fee payer account
- Pyth Lazer program account
- Pyth Lazer storage account
- Pyth Lazer treasury account
- The standard Solana system program account
- The standard Solana instructions sysvar account

You may also add any other accounts your contract needs.

<Callout type="info" icon="💡">
The code snippets below are part of the full consumer contract example
[available on
Github](https://github.com/pyth-network/pyth-examples/tree/main/lazer/solana).
</Callout>

The following code can be used to set up a new instruction within a Solana contract:

Expand All @@ -40,9 +55,13 @@ pub enum Instruction {
/// Update price.
/// Data: `UpdateArgs` followed by a signed Pyth Lazer update.
/// Accounts:
/// 1. sysvar account [readonly] - required for Pyth Lazer
/// 2. data account [writable] - needed by our example contract
/// 3. pyth storage account [readonly] - required for Pyth Lazer
/// 1. payer account
/// 2. example data account [writable]
/// 3. pyth program account [readonly]
/// 4. pyth storage account [readonly]
/// 5. pyth treasury account [writable]
/// 6. system program [readonly]
/// 7. instructions sysvar sysvar account [readonly]
Update = 1,
}

Expand Down Expand Up @@ -82,36 +101,54 @@ pub fn process_update_instruction(
instruction_args: &[u8],
) -> ProgramResult {
// Verify accounts passed to the instruction.
if accounts.len() != 3 {
if accounts.len() != 7 {
return Err(ProgramError::NotEnoughAccountKeys);
}
let sysvar_account = &accounts[0];
let payer_account = &accounts[0];
let data_account = &accounts[1];
let pyth_storage_account = &accounts[2];
let _pyth_program_account = &accounts[2];
let pyth_storage_account = &accounts[3];
let pyth_treasury_account = &accounts[4];
let system_program_account = &accounts[5];
let instructions_sysvar_account = &accounts[6];
// See below for next steps...
}
```

Call `pyth_lazer_sdk::verify_message{:rust}` function with appropriate arguments to validate the Pyth Lazer signature of the message.
Invoke the Pyth Lazer on-chain program with appropriate arguments to validate the Pyth Lazer signature of the message.

```rust copy
// Offset of pyth message within the original instruction_data.
// 1 byte is the instruction id.
let pyth_message_total_offset = size_of::<UpdateArgs>() + 1;
// We expect the instruction to the built-in ed25519 program to be
// the first instruction within the transaction.
let ed25519_instruction_index = 0;
// We expect our signature to be the first (and only) signature to be checked
// by the built-in ed25519 program within the transaction.
let signature_index = 0;
// Check signature verification.
let verified = pyth_lazer_sdk::verify_message(
pyth_storage_account,
sysvar_account,
pyth_message,
ed25519_instruction_index,
signature_index,
pyth_message_total_offset.try_into().unwrap(),
// Verify Lazer signature.
invoke(
&ProgramInstruction::new_with_bytes(
pyth_lazer_solana_contract::ID,
&VerifyMessage {
message_data: pyth_message.to_vec(),
ed25519_instruction_index,
signature_index,
}
.data(),
vec![
AccountMeta::new(*payer_account.key, true),
AccountMeta::new_readonly(*pyth_storage_account.key, false),
AccountMeta::new(*pyth_treasury_account.key, false),
AccountMeta::new_readonly(*system_program_account.key, false),
AccountMeta::new_readonly(*instructions_sysvar_account.key, false),
],
),
&[
payer_account.clone(),
pyth_storage_account.clone(),
pyth_treasury_account.clone(),
system_program_account.clone(),
instructions_sysvar_account.clone(),
],
)?;
```

Expand All @@ -125,7 +162,7 @@ let verified = pyth_lazer_sdk::verify_message(
correctly called in the transaction.
</Callout>

Now Parse the Pyth Lazer message.
Now parse the Pyth Lazer message.

```rust copy
// Deserialize and use the payload.
Expand Down Expand Up @@ -161,6 +198,12 @@ state.latest_price = price.into_inner().into();
state.latest_timestamp = data.timestamp_us.0;
```

<Callout type="info" icon="💡">
Pyth Lazer also provides a [Rust
SDK](https://docs.rs/pyth-lazer-sdk/latest/pyth_lazer_sdk/), which allows
consumers to parse the price updates off-chain.
</Callout>

### Subscribe to Pyth Lazer to receive Price Updates

Pyth Lazer provides a websocket endpoint to receive price updates. Moreover, Pyth Lazer also provides a [typescript SDK](https://github.com/pyth-network/pyth-crosschain/tree/main/lazer/sdk/js) to subscribe to the websocket endpoint.
Expand All @@ -171,6 +214,55 @@ Consult [How to fetch price updates from Pyth Lazer](../fetch-price-updates.mdx)

Now that you have the price updates, and your smart contract is able to parse the price updates, you can include the price updates into the smart contract transactions by passing the price updates received from the previous step as an argument to the `update_price` method of your smart contract.

In order to execute signature verification, you need to include an instruction for the built-in Solana ed25519 program in your transaction.

<Tabs items={['Rust', 'JS']}>
<Tabs.Tab>
In Rust, you can leverage helpers provided in the `pyth_lazer_solana_contract` crate:

```rust copy
// Instruction #0 will be ed25519 instruction;
// Instruction #1 will be our contract instruction.
let instruction_index = 1;
// Total offset of Pyth Lazer update within the instruction data;
// 1 byte is the instruction type.
let message_offset = (size_of::<UpdateArgs>() + 1).try_into().unwrap();
let ed25519_args = pyth_lazer_solana_contract::Ed25519SignatureOffsets::new(
&message,
instruction_index,
message_offset,
);
let mut tx = Transaction::new_with_payer(
&[
Instruction::new_with_bytes(
solana_program::ed25519_program::ID,
&pyth_lazer_solana_contract::ed25519_program_args(&[ed25519_args]),
vec![],
),
Instruction::new_with_bytes(
pyth_lazer_solana_example::ID,
&update_data,
vec![
AccountMeta::new(payer.pubkey(), true),
AccountMeta::new(data_pda_key, false),
AccountMeta::new(pyth_lazer_solana_contract::ID, false),
AccountMeta::new_readonly(pyth_lazer_solana_contract::STORAGE_ID, false),
AccountMeta::new(treasury, false),
AccountMeta::new_readonly(system_program::ID, false),
AccountMeta::new_readonly(sysvar::instructions::ID, false),
],
),
],
Some(&payer.pubkey()),
);
```

</Tabs.Tab>
<Tabs.Tab>
In TypeScript and JavaScript, you can leverage helpers provided in the `@pythnetwork/pyth-lazer-sdk` NPM package.
{/* TODO: add example code */}
</Tabs.Tab>
</Tabs>
</Steps>

## Additional Resources
Expand Down
Loading