feat!: canonical Solana mainnet+devnet deployment, base58 helpers (v0.4.0) - #22
Draft
reednaa wants to merge 4 commits into
Draft
feat!: canonical Solana mainnet+devnet deployment, base58 helpers (v0.4.0)#22reednaa wants to merge 4 commits into
reednaa wants to merge 4 commits into
Conversation
….4.0) The shipped Solana constants were PDAs derived under the pre-vanity program ids, superseded when catalyst-intent-svm 21fc982 rotated every program to its LiFi vanity key. They are correctly-shaped and correctly-derived, which is why they went unnoticed: an order carrying them can never be filled. Replaces them with the canonical deployment, and separates the three values that all get called "the Solana Polymer oracle" but are not interchangeable: MandateOutput.settler -> output settler PDA MandateOutput.oracle -> polymer PROGRAM ID StandardSolana.inputOracle -> polymer oracle PDA buildMandateOutputs set output.oracle to the *input* chain's oracle for every cross-chain Polymer output. That rule only holds because PolymerOracle is CREATE2-identical across EVM chains; on Solana, oracle_polymer::submit compares the fill's LocalAttestation consumer against its own program id. Every EVM->Solana order the builder produced was therefore unprovable — the tokens move, then proving fails. Solana outputs now resolve their own program id. Also: - new src/helpers/solana.ts: solanaBase58ToBytes32, bytes32ToSolanaBase58, isSolanaBase58Address. No new dependency (@scure/base was already present). Documents that Solana base58 is raw — no checksum, so a typo decodes to a different valid key and the 32-byte length check is the only defence. - PDA/program-id accessors for symmetry with the Tron ones. - Solana same-chain orders no longer require a configured oracle; they point inputOracle at the output settler like EVM and Tron, since validate_fill takes the LocalAttestation branch and never reads it. - exclusiveFor accepts a 32-byte Solana solver pubkey; the 20-byte-only guard made exclusive fills structurally impossible on Solana outputs. - validation rejects duplicate outputs (one transfer satisfies both fill records) and Solana output amounts above u64. - constants.spec re-derives every PDA from (program id, seed, canonical bump) and pins the superseded values as forbidden. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
12 tasks
reednaa
added a commit
to lifinance/lintent
that referenced
this pull request
Aug 13, 2026
Temporary local `file:` dependency on ../intent.ts so this branch can build against the Solana work in lifinance/intent.ts#22 before it is published. This MUST be repinned to a published version before merge: "@lifi/intent": "0.4.0" CI and the Cloudflare Worker build cannot resolve a file: path, so this branch is expected to be red until the library lands. Restores the Solana branch of formatAddressForChain, which was stubbed in the previous commit because bytes32ToSolanaBase58 did not exist in 0.2.1. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The exports map listed `"node": "./_cjs/index.js"` ahead of `"import"`.
Condition order is first-match, so `node` won for every Node-side consumer
regardless of how they imported — an `import` from an ESM context got the
CommonJS build.
Node itself tolerates that, but a bundler evaluating the package in an ESM
module runner does not: SvelteKit's SSR inlines the CJS entry and dies on
`ReferenceError: require is not defined`, taking down the whole route that
imported it. Found in lintent, where a server route importing this package
returned 500 for every request including malformed ones — the module never
loaded, so no handler code ran.
`node` now branches on import/require rather than short-circuiting both to
CJS. `_esm/package.json` already declares `{"type":"module"}`, so the ESM
build was always correct to hand out; nothing else changes and `require()`
still resolves to `_cjs`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The quote API reads every address and asset in the CAIP-2 namespace its
sibling `chain` field declares. A Solana mint sent as 32-byte hex is
rejected with `bytes32 value has non-zero upper bytes`, because an
`eip155` asset is read as a left-padded 20-byte EVM address and a full
Solana key never fits. The same error appears under a correct `solana:`
chain still carrying hex, so the response cannot tell a wrong namespace
from a wrong encoding.
`toQuoteAddress` converts on the way out, from the `namespace` already
supplied alongside each chain id, so callers keep one internal
representation. Native base58 passes through unchanged. EVM and Tron are
deliberately untouched: the API accepts their hex today, and Tron base58
is a different (checksummed) encoding that should only be introduced
against a verified API expectation.
The address fields widen from `0x${string}` to `string` so a caller
holding a native Solana address needs no conversion of its own.
Verified against order.li.fi: the corrected payload returns HTTP 200.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An omitted `namespace` silently defaults to eip155, which sends `eip155:1151111081099710` with an unconverted 32-byte asset. The API then answers `bytes32 value has non-zero upper bytes`, naming the asset rather than the namespace that actually caused it. Fail locally instead, where the message can say which field to set. EVM and Tron are both 20 bytes, so a 32-byte value under either is always a caller mistake and never a legitimate address. Also records why Tron is not converted: order.li.fi was probed live with hex, base58 and mixed asset/receiver on a `tron:` output and returned 200 for all three. Solana is the only namespace that hard-rejects. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Impact — high severity
Solana order construction was unsafe for both mainnet and devnet:
buildMandateOutputsplaced the input chain’s EVM oracle address inMandateOutput.oracle. The Solana fill could transfer the user’s requested tokens, but the subsequent Polymer submission rejected the attestation consumer, leaving the fill unprovable.This affects consumers using
@lifi/intentto construct orders involving the canonical Solana mainnet or devnet deployment. Solana testnet remains unsupported.What changed
catalyst-intent-svm.exclusiveFor.inputOraclefor same-chain Solana orders, where the local-attestation branch does not readinputOracle.u64.Mainnet and devnet use identical program IDs in
catalyst-intent-svm/Anchor.toml; consequently their PDAs are also identical. Testnet has no configured deployment.Non-interchangeable Solana values
These values serve different protocol roles and must not be substituted for one another:
MandateOutput.settlerDHShHmVkTwCzUzAQbCu4GDqJmursuDscNR6o4hTBgeRy0xb68296ce230150bb20190a46eb26a198b05bec8fc7f0fa893d690cf531fa9e54programs/outputs/output_settler_base/src/base.rs:122validatesoutput.settleragainst the output-settler account key.MandateOutput.oraclefor a Solana outputLiFiBtfyPT1DnTHTAeZ2rwr5RgMrThwA5kt7KGT5nBV0x050cae5588f8d907500199177ab1239f61b8557af2ffacd5defed0070b858ad4programs/oracles/oracle_polymer/src/instructions/submit.rs:71-79requires the local-attestation consumer to matchctx.program_id, Polymer’sreturnedProgramId.StandardSolana.inputOraclefor a remote fill49zLKETMq34CUC2E2wL1xvv6uN2AUgyhjVX221mjE3Rw0x2ee088ace4ac030d2266e50d829feeac73d1acb13e5825ef835d6f3318804796programs/oracles/oracle_polymer/src/instructions/receive_attest.rs:142-150creates the remote attestation throughctx.accounts.oracle_polymer;programs/inputs/input_settler_base/src/base.rs:125-130validates that attestation usinginput_oracle.For a same-chain Solana fill,
inputOracleis instead set canonically to the output-settler PDA becausevalidate_filltakes the local-attestation branch atprograms/inputs/input_settler_base/src/base.rs:88-111and does not readinput_oracle.Canonical Solana deployment
The following program IDs are declared identically for mainnet and devnet in
catalyst-intent-svm/Anchor.toml:8-20.LiFixdGLT5CMdLsHBvaijTXpPy4Uux9Y53SXkuR4HaK0x050cae668656471e02181ee1e6c52ee00a779ca7c481a04fb0e2ab9085f5c304LiFiRp8RM7nJUZyUYC9FPPpDr7sAy5XPfBN6ABzBgT70x050cae5ad286a6a6227a55fd650d63a3eccc866d6c293c94b87839e5cb6dba86LiFiEDFjz5x1jJe9gSXNDHQW4dWt4yLXdp2VN4EiQUt0x050cae566a93270d66dbfe1915b80dc06e4a80178992592b3d8f51e888d0afe5LiFiBtfyPT1DnTHTAeZ2rwr5RgMrThwA5kt7KGT5nBV0x050cae5588f8d907500199177ab1239f61b8557af2ffacd5defed0070b858ad4chain_id, bump255BkEw3WHFvJR9a5deUcwPLJ79yK3r9YGdQ61TehFXzAQ0x02c0b32f8be4a5319a95cca17bd05eba48e7195ce52f1608723b51e575457df5input_settler_escrow, bump253Cj3mSoPJtgi5bubC9rLz8oM1DuXoJj97RMw1uC6ev9zm0xae3613f974fc9cd94682bbff7bd7f229697616c5b6fdb6e0c16d1f02607242aeoutput_settler_simple, bump255DHShHmVkTwCzUzAQbCu4GDqJmursuDscNR6o4hTBgeRy0xb68296ce230150bb20190a46eb26a198b05bec8fc7f0fa893d690cf531fa9e54polymer, bump25349zLKETMq34CUC2E2wL1xvv6uN2AUgyhjVX221mjE3Rw0x2ee088ace4ac030d2266e50d829feeac73d1acb13e5825ef835d6f3318804796buildMandateOutputsbugThe existing Polymer rule was:
That is correct for EVM→EVM orders because the EVM
PolymerOracleis deployed CREATE2-identically across supported EVM chains. Proofs are therefore keyed under the sameaddress(this)value, and selecting the input chain’s configured oracle yields the expected identifier.That assumption does not extend to Solana. For a Solana output:
LocalAttestationwhose consumer isMandateOutput.oracle.returnedProgramId.oracle_polymer::submitvalidates the local attestation withctx.program_idas the expected consumer.buildMandateOutputsnow handles Polymer Solana outputs before applying the generic EVM rule:Breaking changes
This package is still
0.x. The release moves from0.3.0to0.4.0because existing exported Solana values change and previously constructed orders may encode different bytes.Existing exported names whose values change:
SOLANA_MAINNET_INPUT_SETTLER_ESCROWSOLANA_DEVNET_INPUT_SETTLER_ESCROWSOLANA_MAINNET_OUTPUT_SETTLER_PDASOLANA_DEVNET_OUTPUT_SETTLER_PDASOLANA_INPUT_SETTLER_PROGRAMSSOLANA_OUTPUT_SETTLER_PDASinputSettlerForSolana(...)consequently returns the canonical escrow PDA instead of the superseded PDA.New exported deployment names and registries:
SOLANA_INTENTS_PROTOCOL_PROGRAMSOLANA_INPUT_SETTLER_ESCROW_PROGRAMSOLANA_OUTPUT_SETTLER_SIMPLE_PROGRAMSOLANA_POLYMER_ORACLE_PROGRAMSOLANA_CHAIN_ID_PDASOLANA_INPUT_SETTLER_ESCROW_PDASOLANA_OUTPUT_SETTLER_PDASOLANA_POLYMER_ORACLE_PDASOLANA_INPUT_SETTLER_ESCROW_PROGRAMSSOLANA_OUTPUT_SETTLER_PROGRAMSSOLANA_POLYMER_ORACLESSOLANA_POLYMER_ORACLE_PROGRAMSNew package-root helpers:
inputSettlerForSolanainputSettlerProgramForSolanaoutputSettlerForSolanapolymerOracleForSolanapolymerOracleProgramForSolanasolanaBase58ToBytes32bytes32ToSolanaBase58isSolanaBase58AddressSOLANA_ADDRESS_BYTESValidation also gains
OUTPUT_AMOUNT_EXCEEDS_U64andDUPLICATE_OUTPUTS, so orders previously accepted by the generic validator can now be rejected when they cannot be safely filled.Testing
The new specs prove:
ProgramDerivedAddressmarker.exclusiveForaccepts an unpadded 32-byte solver public key.u64::MAX, while acceptingu64::MAXand supported native SOL outputs.Full suite (
bun test), plusbun run checkandbun run buildincluding theESM/CJS export smoke check:
Merge order / release
This branch is based on
fix/fill-description-domain-magic(v0.3.0, commitc2f32a1), which has not yet been merged tomain.Merge
fix/fill-description-domain-magicfirst, then merge this PR. Releasing this branch independently would also include the pendingv0.3.0domain-magic changes.The package version is bumped to
0.4.0. Publication is triggered by pushing thev0.4.0tag after the merge-order dependency and release questions below are resolved.Review notes / open questions
OrderContainer.inputSettlersemantics for Solana require owner confirmation before release.SOLANA_INPUT_SETTLER_PROGRAMSandinputSettlerForSolana(...)currently hold/return the input-settler escrow PDA, and this PR updates that PDA to its canonical value. It is not yet confirmed whether the order service expectsOrderContainer.inputSettlerto contain that escrow PDA or the callable input-settler program ID. The order-service owner should confirm the contract beforev0.4.0is tagged.catalyst-intent-svm/Anchor.toml, and the PDAs are deterministically re-derived, but this PR does not verify on-chain that every program and required state PDA has been deployed and initialized correctly on each advertised cluster. Mainnet and devnet initialization state, chain IDs, ownership, and required Polymer mappings should be checked before release.