Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 2 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ jobs:
- name: Lint rust
shell: bash
run: yarn lint-rust
- name: Test rust
run: cargo test -p svm-spoke --lib
- name: Regenerate constants.json
run: yarn generate-constants-json && yarn prettier --write generated/constants.json
- name: Regenerate deployed-addresses
Expand Down
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion programs/svm-spoke/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,9 @@ test = []
anchor-lang = { version = "0.31.1", features = ["init-if-needed","event-cpi"]}
anchor-spl = "0.31.1"
multicall-handler = { path = "../multicall-handler" }
solana-security-txt = "1.1.1"
solana-security-txt = "1.1.1"

[dev-dependencies]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These dev-deps back 7 tests that CI will never run.

.github/workflows/pr.yml has lint-rust (cargo +nightly fmt --all -- --check && cargo clippy) but no cargo test anywhere in the file. The TS vectors run via anchor test and the Solidity vectors via test-evm, so two of the three languages are covered — but the Rust half, which is the one that actually exercises decode_v5_adapter_input, resolve_v5_deposit_modifications, and the PDA derivations, is unguarded.

Given the whole point of this PR is a frozen surface protected by golden vectors, that's the gap most likely to let drift through. A step in the existing lint-and-check-generated job (it already installs a Rust toolchain and warms the cargo cache) would cover it:

- name: Test rust
  run: cargo test -p svm-spoke --lib

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added cargo test -p svm-spoke --lib to the existing lint-and-check-generated job, immediately after Rust lint. The expanded local suite now passes 9 tests.


Sent from Reinis Martinsons's Codex CLI Agent using gpt-5.6-sol 🤖

hex = "0.4.3"
libsecp256k1 = "0.7.2"
serde_json = "1.0"
117 changes: 117 additions & 0 deletions programs/svm-spoke/V5_ADAPTER_SPEC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# SVM SpokePool V5 adapter specification (wire version 1)

This document freezes the compatibility surface for the Gateway-facing `svm_spoke` V5 adapter. It intentionally
describes foundations only: wire version 1 does not become callable until the source and destination behavior steps
land.

## Dispatch ABI and accounts

The frozen dispatch target for the later behavior steps is the single
`adapter_execute_across_v5(ctx_values, input, jit_data)` entrypoint, whose Anchor discriminator is the first eight
bytes of `sha256("global:adapter_execute_across_v5")`. Gateway program
`34trBszXuqhRjWaMxXWsunJNmyUsBvDNPxAwTzbPTm4p` serializes:

```text
discriminator[8]
|| step_id[32] || path_id[32] || submitter[32]
|| input_len:u32_le || input
|| jit_len:u32_le || jit_data
```

The common fixed Anchor accounts, in order, are:

1. `dispatch_authority`: read-only signer, PDA
`["dispatch_authority", svm_spoke::ID]` under Gateway;
2. `state`: read-only `svm_spoke` state PDA;
3. `event_authority`: read-only `["__event_authority"]` PDA under `svm_spoke`;
4. `program`: read-only executable `svm_spoke::ID` account used by Anchor event CPI.

All token, mint, token-program, vault, delegate, fill-status, payer, ATA-program, and system-program accounts are
branch-specific remaining accounts. The implementation derives every expected key and searches by key; caller order
does not authenticate an account. Accounts that can lose lamports or whose data/token amount can change must also be
writable at the transaction level.

## Committed input and JIT wire

`input` is strict Borsh with no trailing bytes:

```text
V5AdapterInput {
version: u8 = 1,
mode: enum { Deposit = 0(AcrossDepositInput), Fill = 1(V5FillInput) }
}
```

`AcrossDepositInput` nests the canonical deposit fields under `deposit_params: AcrossDepositParams`, matching the EVM
adapter's type boundary. Borsh serializes that fixed struct inline, so the nesting adds no bytes. All Rust fields
serialize in declaration order. Integers use Borsh little-endian encoding. Pubkeys and `[u8; 32]` are raw 32-byte
values. Vectors use a `u32_le` length. `input_amount_mode` is `Literal = 0` or
`InputVaultBalance = 1 { bips: u16_le }`; `bips` must not exceed 10,000. The resolved SVM token amount is `u64`, while
cross-VM uint256 values remain 32-byte big-endian EVM words. The leading version byte is checked before the mode body
is decoded, so any unsupported version reports `UnsupportedVersion` even when its body is not compatible with v1.

Gateway token vaults are shared per mint rather than isolated per execution. `InputVaultBalance` therefore resolves
against shared live state, and the continuing tape must leave no residual balance or stale approval that a later
permissionless execution could consume. Gateway does not currently enforce this net-zero settlement invariant.

Unlike the EVM `inputAmountParam`, SVM wire v1 has no set-call-value flag. Native SOL must first be wrapped by the
ordinary Gateway `WRAP_SOL` command into its canonical WSOL vault; the deposit then consumes WSOL through the same
token path as any SPL input. Direct lamport deposit from this adapter is outside wire v1.

Deposit JIT uses the EVM-aligned name `AcrossDepositJitParams` and is present exactly when the committed 20-byte
authority is nonzero and at least one modification is permitted. It is the fixed 129 bytes
`new_output_amount[32] || new_exclusive_relayer[32] || signature[65]`. A zero authority requires both permission
booleans false and empty `jit_data`; it never means permissionless modification. This intentionally diverges from
the EVM `AcrossDepositDelegateAdapter`, which permits authority-less JIT when a permission flag is set. Route builders
must not emit that EVM-only rule shape for SVM. Fill mode always decodes `jit_data` as `V5FillJit`. Malformed enum tags,
invalid Borsh booleans or lengths, unsupported versions, missing required JIT, and trailing bytes fail closed.

## Hashes and signatures

Canonical EVM integer encoding below means a 32-byte big-endian uint256 word:

```text
synthetic_nonce = keccak256(submitter[32] || path_id[32] || uint256(deposit_nonce:u64))
deposit_id = keccak256(executor_program_id[32] || depositor[32] || synthetic_nonce)

name_hash = keccak256("ACXV.AcrossDepositDelegateAdapter.V1")
domain = keccak256(name_hash || gateway_program_id[32])
digest = keccak256(
domain || path_id || uint256(deposit_nonce:u64) || new_output_amount[32] || new_exclusive_relayer[32]
)
```

The configured executor is Gateway in version 1, but deposit identity deliberately takes `executor_program_id` while
the signature domain always takes `gateway_program_id`. Signatures are secp256k1 `r[32] || s[32] || v[1]`, accept
only `v` 27 or 28, require low `s`, recover an uncompressed public key, and compare the last 20 bytes of its Keccak
hash with the committed authority. ERC-1271, Ed25519, EIP-2098, high-`s`, and `v` 0/1 encodings are unsupported.

## PDA and token invariants

- Source delegate: `["v5_source_delegate"]` under `svm_spoke`; a preceding ordinary Gateway `APPROVE` may grant any
allowance at least the resolved amount, including `u64::MAX`. `svm_spoke` later pulls exactly the resolved amount.
- External fill delegate: `["v5_fill_delegate"]` under `svm_spoke`; sufficient allowance is accepted and the exact
JIT `output_amount` is pulled.
- Gateway vault authority: `["vault_authority"]` under Gateway. A Gateway vault is the canonical ATA of this authority,
the mint, and the mint's token program.
- Fill status: the existing `["fills", relay_hash]` PDA under `svm_spoke`, preserving the standard replay namespace.
- Fill payer float: `["v5_fill_payer", submitter]` under `svm_spoke`. The data-less, system-owned PDA manually pays
fill-status rent with `invoke_signed`; it is not a forwarded transaction signer.

An external delivery targets the canonical ATA of committed `recipient`, output mint, and token program. A canonical
Gateway-vault delivery validates that same live vault in place and its amount, records the fill, and performs no token
self-transfer or approval. The continuing atomic tape must consume the output.

Fill-status expiry reclaim is permissionless and closes back to the submitter-scoped payer PDA, replenishing its
standing float. Only that submitter may withdraw the float to itself; a nonzero remainder must be rent-exempt, and
`u64::MAX` means withdraw the live balance. V5 fills emit the existing `FilledRelay` schema and derive the relay hash
from the supplied standard `RelayData` and the configured SVM chain ID. Adapter mode requires an empty callback
message; the relay witness remains exactly `V5_MAGIC_PREFIX || step_id`.

Transfer-fee mints are excluded until debit/delivery delta semantics are defined. Transfer hooks remain disabled
unless validator tests prove the complete hook-account set and the Gateway-to-Spoke CPI depth for that mint.

Golden values in `fixtures/v5_adapter_v1.json` are independently re-derived from Rust, TypeScript, and Solidity to
catch byte-width, packing, and endianness drift. These are cross-language self-consistency vectors, not an invocation
of the EVM adapter. The JIT digest layout matches `AcrossDepositDelegateAdapter`, while SVM deposit identity
necessarily uses a 32-byte executor program ID instead of EVM's 20-byte caller address.
70 changes: 70 additions & 0 deletions programs/svm-spoke/fixtures/v5_adapter_v1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"version": 1,
"programs": {
"gateway": "34trBszXuqhRjWaMxXWsunJNmyUsBvDNPxAwTzbPTm4p",
"gatewayBytes": "0x1eb6bdb6c483ad6e0d22ef22f9e874b14721fe0fa978038dc0da56d201337afd",
"svmSpoke": "DLv3NggMiSaef97YCkew5xKUHDh13tVGZ7tydt3ZeAru",
"svmSpokeBytes": "0xb7664086de37ee70821c10445b162f2c7ec8795bd0800c1462949e2328d1dd5a"
},
"context": {
"stepId": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"pathId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"submitter": "0x1111111111111111111111111111111111111111111111111111111111111111",
"borsh": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb1111111111111111111111111111111111111111111111111111111111111111"
},
"deposit": {
"depositor": "0x2222222222222222222222222222222222222222222222222222222222222222",
"recipient": "0x3333333333333333333333333333333333333333333333333333333333333333",
"inputToken": "0x4444444444444444444444444444444444444444444444444444444444444444",
"outputToken": "0x5555555555555555555555555555555555555555555555555555555555555555",
"inputAmount": "123456789",
"outputAmount": "0x000000000000000000000000000000000000000000000000000000003ade68b1",
"destinationChainId": "10",
"exclusiveRelayer": "0x6666666666666666666666666666666666666666666666666666666666666666",
"depositNonce": "72623859790382856",
"quoteTimestamp": 1700000000,
"fillDeadline": 1700003600,
"exclusivityParameter": 300,
"dstStepId": "0x7777777777777777777777777777777777777777777777777777777777777777",
"inputAmountMode": { "discriminant": 1, "bips": 9750 },
"syntheticNonce": "0x92ecdb528d6aa8899a892e24114411e86cc79a81f6e8b77e9ebc96f7c5a184ff",
"depositId": "0x48ae46119d5a506c6631ead56d5ba2abb3be5119f8da5d0837c343050a6789e6"
},
"jit": {
"authority": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"nameHash": "0x175bcc73211bd512c12efdaae2b1162ec659fc84230de0a1e28f01ef2a718233",
"domain": "0x1701a2abc908d37c1da054beecc5d079267528ce4cbb951426da25f02acf430d",
"newOutputAmount": "0x000000000000000000000000000000000000000000000000000000003b9ac9ff",
"newExclusiveRelayer": "0x8888888888888888888888888888888888888888888888888888888888888888",
"digest": "0xf76ca8256c0bc879e0bc1071b2d6e23f6bc1b0b7c2a8d661174b1326cc988a45",
"signature": "0x71a0f40cf5a11bda73a26cefd7a7b818b1632be2fd9845aabf3e094421d6b96b51de6d353a148d0d7f4a67778f6ac5d94886b912a365e556b025b2611408165a1c",
"highSSignature": "0x71a0f40cf5a11bda73a26cefd7a7b818b1632be2fd9845aabf3e094421d6b96bae2192cac5eb72f280b5988870953a25722823d40be2bae50facac2bbc2e2ae71b"
},
"fill": {
"minOutputAmount": "950000000",
"outputAmount": "960000000",
"originChainId": "1",
"exclusivityDeadline": 1700000300,
"repaymentChainId": "1",
"repaymentAddress": "0x9999999999999999999999999999999999999999999999999999999999999999",
"witness": "0x89ae4bc75915265a3f10e926c3894a29534f1d6362ee8959cb0e5be00f3527fdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
},
"wire": {
"depositInput": "0x0100222222222222222222222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555515cd5b0700000000000000000000000000000000000000000000000000000000000000003ade68b10a000000000000006666666666666666666666666666666666666666666666666666666666666666080706050403020100f1536510ff53652c0100007777777777777777777777777777777777777777777777777777777777777777011626f39fd6e51aad88f6f4ce6ab8827279cfffb922660101",
"depositJit": "0x000000000000000000000000000000000000000000000000000000003b9ac9ff888888888888888888888888888888888888888888888888888888888888888871a0f40cf5a11bda73a26cefd7a7b818b1632be2fd9845aabf3e094421d6b96b51de6d353a148d0d7f4a67778f6ac5d94886b912a365e556b025b2611408165a1c",
"fillInput": "0x01013333333333333333333333333333333333333333333333333333333333333333555555555555555555555555555555555555555555555555555555555555555580d99f380000000000000000",
"fillJit": "0x2222222222222222222222222222222222222222222222222222222222222222333333333333333333333333333333333333333333333333333333333333333388888888888888888888888888888888888888888888888888888888888888884444444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555500000000000000000000000000000000000000000000000000000000075bcd150070383900000000010000000000000048ae46119d5a506c6631ead56d5ba2abb3be5119f8da5d0837c343050a6789e610ff53652cf253654000000089ae4bc75915265a3f10e926c3894a29534f1d6362ee8959cb0e5be00f3527fdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa01000000000000009999999999999999999999999999999999999999999999999999999999999999"
},
"dispatch": {
"discriminator": "0x17a145949680addc",
"data": "0x17a145949680addcaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb11111111111111111111111111111111111111111111111111111111111111111f0100000100222222222222222222222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555515cd5b0700000000000000000000000000000000000000000000000000000000000000003ade68b10a000000000000006666666666666666666666666666666666666666666666666666666666666666080706050403020100f1536510ff53652c0100007777777777777777777777777777777777777777777777777777777777777777011626f39fd6e51aad88f6f4ce6ab8827279cfffb92266010181000000000000000000000000000000000000000000000000000000000000003b9ac9ff888888888888888888888888888888888888888888888888888888888888888871a0f40cf5a11bda73a26cefd7a7b818b1632be2fd9845aabf3e094421d6b96b51de6d353a148d0d7f4a67778f6ac5d94886b912a365e556b025b2611408165a1c"
},
"pdas": {
"dispatchAuthority": { "address": "28uSGvM8TVkVkAq9LBZASDPEi5t6RFbVekXECbDMqrWs", "bump": 253 },
"gatewayVaultAuthority": { "address": "D3rtRbTdrBx8x8wp6xmQcUeS6seSt9nBB7DUG5S86HHb", "bump": 254 },
"sourceDelegate": { "address": "CVh7qLq3yviU8iGuBmmgipJpfy95p9B81e2YTWDaxCbb", "bump": 255 },
"fillDelegate": { "address": "D27f3mVXRL6N3bgja49UWLQu7kt57sy1aZYy7ZEwdxn1", "bump": 252 },
"fillPayer": { "address": "Dpq2pxBDexzTiaQ35qoyyH1rF5siNyrSJhrdGMbWdBnF", "bump": 250 },
"fillStatus": { "address": "2bdk4WnigBmjqwHtp1HQ5DHySvZ9QirEw9c8tWMXkfma", "bump": 255 }
}
}
14 changes: 14 additions & 0 deletions programs/svm-spoke/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@ pub const MESSAGE_TRANSMITTER_PROGRAM_ID: Pubkey = pubkey!("CCTPmbSD7gX1bxKPAmg7
pub const MAX_EXCLUSIVITY_PERIOD_SECONDS: u32 = 31_536_000;

pub const ZERO_DEPOSIT_ID: [u8; 32] = [0u8; 32];
pub const BIPS_DENOMINATOR: u16 = 10_000;

pub const V5_ADAPTER_WIRE_VERSION: u8 = 1;
pub const V5_SOURCE_DELEGATE_SEED: &[u8] = b"v5_source_delegate";
pub const V5_FILL_DELEGATE_SEED: &[u8] = b"v5_fill_delegate";
pub const V5_FILL_PAYER_SEED: &[u8] = b"v5_fill_payer";
pub const FILL_STATUS_SEED: &[u8] = b"fills";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constant is introduced but the three existing call sites still use the bare literal — instructions/fill.rs:70, instructions/slow_fill.rs:36, instructions/slow_fill.rs:167.

Right now there are two sources of truth for the same replay namespace, which is the opposite of what extracting the constant was for. Anchor's seeds = [...] accepts a const, so they can all take FILL_STATUS_SEED in this PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed all three existing account constraints to use FILL_STATUS_SEED; there are no remaining b"fills" seed literals in those fill/slow-fill call sites.


Sent from Reinis Martinsons's Codex CLI Agent using gpt-5.6-sol 🤖


// Mirrored from the Gateway dispatch ABI. Keep these local: Gateway and svm-spoke intentionally use different
// Anchor versions and must not acquire a cross-repository Rust dependency.
pub const GATEWAY_PROGRAM_ID: Pubkey = pubkey!("34trBszXuqhRjWaMxXWsunJNmyUsBvDNPxAwTzbPTm4p");
pub const GATEWAY_DISPATCH_AUTHORITY_SEED: &[u8] = b"dispatch_authority";
pub const GATEWAY_VAULT_AUTHORITY_SEED: &[u8] = b"vault_authority";
pub const GATEWAY_ADAPTER_EXECUTE_V5_PREIMAGE: &[u8] = b"global:adapter_execute_across_v5";

// Magic prefix tagging a deposit message as an Across V5 witness: `message = V5_MAGIC_PREFIX || stepId`, where
// stepId is the Merkle root of the Gateway execution allowed to consume the deposit. V5-tagged deposits are only
Expand Down
25 changes: 25 additions & 0 deletions programs/svm-spoke/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,31 @@ pub enum SvmError {
InvalidDelegatePda,
}

// Across V5 adapter specific errors.
#[error_code(offset = 7000)]
pub enum V5Error {
#[msg("Malformed Across V5 wire data!")]
InvalidWireFormat,
#[msg("Unsupported Across V5 wire version!")]
UnsupportedVersion,
#[msg("Invalid Across V5 parameter modification rules!")]
InvalidParamModificationRules,
#[msg("Invalid Across V5 parameter modification signature!")]
InvalidParamModificationSignature,
#[msg("Missing required Across V5 account!")]
MissingAccount,
#[msg("Invalid Across V5 Gateway dispatch authority!")]
InvalidDispatchAuthority,
#[msg("Across V5 account must be writable!")]
InvalidAccountMutability,
#[msg("Resolved Across V5 input amount is below the committed floor!")]
ResolvedInputAmountBelowCommitted,
#[msg("Across V5 delegate allowance is insufficient!")]
InsufficientDelegateAllowance,
#[msg("Across V5 parameter modification is not an improvement!")]
ParamModificationNotAnImprovement,
}

// CCTP specific errors.
#[error_code]
pub enum CallDataError {
Expand Down
4 changes: 2 additions & 2 deletions programs/svm-spoke/src/instructions/fill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use anchor_spl::{

use crate::{
common::RelayData,
constants::DISCRIMINATOR_SIZE,
constants::{DISCRIMINATOR_SIZE, FILL_STATUS_SEED},
constraints::is_relay_hash_valid,
error::{CommonError, SvmError},
event::{FillType, FilledRelay, RelayExecutionEventInfo},
Expand Down Expand Up @@ -67,7 +67,7 @@ pub struct FillRelay<'info> {
init_if_needed,
payer = signer,
space = DISCRIMINATOR_SIZE + FillStatusAccount::INIT_SPACE,
seeds = [b"fills", relay_hash.as_ref()],
seeds = [FILL_STATUS_SEED, relay_hash.as_ref()],
bump,
constraint = is_relay_hash_valid(
&relay_hash,
Expand Down
6 changes: 3 additions & 3 deletions programs/svm-spoke/src/instructions/slow_fill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use anchor_spl::token_interface::{transfer_checked, Mint, TokenAccount, TokenInt
use crate::event::{FillType, FilledRelay, RelayExecutionEventInfo, RequestedSlowFill};
use crate::{
common::RelayData,
constants::DISCRIMINATOR_SIZE,
constants::{DISCRIMINATOR_SIZE, FILL_STATUS_SEED},
constraints::is_relay_hash_valid,
error::{CommonError, SvmError},
state::{ExecuteSlowRelayLeafParams, FillStatus, FillStatusAccount, RequestSlowFillParams, RootBundle, State},
Expand Down Expand Up @@ -33,7 +33,7 @@ pub struct RequestSlowFill<'info> {
init_if_needed,
payer = signer,
space = DISCRIMINATOR_SIZE + FillStatusAccount::INIT_SPACE,
seeds = [b"fills", _relay_hash.as_ref()],
seeds = [FILL_STATUS_SEED, _relay_hash.as_ref()],
bump,
constraint = is_relay_hash_valid(
&_relay_hash,
Expand Down Expand Up @@ -164,7 +164,7 @@ pub struct ExecuteSlowRelayLeaf<'info> {

#[account(
mut,
seeds = [b"fills", _relay_hash.as_ref()],
seeds = [FILL_STATUS_SEED, _relay_hash.as_ref()],
bump,
// Make sure caller provided relay_hash used in PDA seeds is valid.
constraint = is_relay_hash_valid(
Expand Down
1 change: 1 addition & 0 deletions programs/svm-spoke/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub mod event;
mod instructions;
mod state;
pub mod utils;
pub mod v5;

use common::*;
use instructions::*;
Expand Down
Loading
Loading