@sip-protocol/sdk 0.7.2 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +267 -0
- package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
- package/dist/browser.d.mts +10 -4
- package/dist/browser.d.ts +10 -4
- package/dist/browser.js +48874 -18336
- package/dist/browser.mjs +674 -48
- package/dist/chunk-4GRJ5MAW.mjs +152 -0
- package/dist/chunk-5D7A3L3W.mjs +717 -0
- package/dist/chunk-64AYA5F5.mjs +7834 -0
- package/dist/chunk-GMDGB22A.mjs +379 -0
- package/dist/chunk-I534WKN7.mjs +328 -0
- package/dist/chunk-IBZVA5Y7.mjs +1003 -0
- package/dist/chunk-PRRZAWJE.mjs +223 -0
- package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
- package/dist/chunk-YWGJ77A2.mjs +33806 -0
- package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
- package/dist/constants-LHAAUC2T.mjs +51 -0
- package/dist/dist-2OGQ7FED.mjs +3957 -0
- package/dist/dist-IFHPYLDX.mjs +254 -0
- package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
- package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
- package/dist/index-DXh2IGkz.d.ts +24681 -0
- package/dist/index-DeE1ZzA4.d.mts +24681 -0
- package/dist/index.d.mts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +48676 -17318
- package/dist/index.mjs +583 -19
- package/dist/interface-Bf7w1PLW.d.mts +679 -0
- package/dist/interface-Bf7w1PLW.d.ts +679 -0
- package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
- package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
- package/dist/proofs/halo2.d.mts +151 -0
- package/dist/proofs/halo2.d.ts +151 -0
- package/dist/proofs/halo2.js +350 -0
- package/dist/proofs/halo2.mjs +11 -0
- package/dist/proofs/kimchi.d.mts +160 -0
- package/dist/proofs/kimchi.d.ts +160 -0
- package/dist/proofs/kimchi.js +431 -0
- package/dist/proofs/kimchi.mjs +13 -0
- package/dist/proofs/noir.d.mts +1 -1
- package/dist/proofs/noir.d.ts +1 -1
- package/dist/proofs/noir.js +74 -18
- package/dist/proofs/noir.mjs +84 -24
- package/dist/solana-U3MEGU7W.mjs +280 -0
- package/dist/validity_proof-3POXLPNY.mjs +21 -0
- package/package.json +54 -21
- package/src/adapters/index.ts +41 -0
- package/src/adapters/jupiter.ts +571 -0
- package/src/adapters/near-intents.ts +135 -0
- package/src/advisor/advisor.ts +653 -0
- package/src/advisor/index.ts +54 -0
- package/src/advisor/tools.ts +303 -0
- package/src/advisor/types.ts +164 -0
- package/src/chains/ethereum/announcement.ts +536 -0
- package/src/chains/ethereum/bnb-optimizations.ts +474 -0
- package/src/chains/ethereum/commitment.ts +522 -0
- package/src/chains/ethereum/constants.ts +462 -0
- package/src/chains/ethereum/deployment.ts +596 -0
- package/src/chains/ethereum/gas-estimation.ts +538 -0
- package/src/chains/ethereum/index.ts +268 -0
- package/src/chains/ethereum/optimizations.ts +614 -0
- package/src/chains/ethereum/privacy-adapter.ts +855 -0
- package/src/chains/ethereum/registry.ts +584 -0
- package/src/chains/ethereum/rpc.ts +905 -0
- package/src/chains/ethereum/stealth.ts +491 -0
- package/src/chains/ethereum/token.ts +790 -0
- package/src/chains/ethereum/transfer.ts +637 -0
- package/src/chains/ethereum/types.ts +456 -0
- package/src/chains/ethereum/viewing-key.ts +455 -0
- package/src/chains/near/commitment.ts +608 -0
- package/src/chains/near/constants.ts +284 -0
- package/src/chains/near/function-call.ts +871 -0
- package/src/chains/near/history.ts +654 -0
- package/src/chains/near/implicit-account.ts +840 -0
- package/src/chains/near/index.ts +393 -0
- package/src/chains/near/native-transfer.ts +658 -0
- package/src/chains/near/nep141.ts +775 -0
- package/src/chains/near/privacy-adapter.ts +889 -0
- package/src/chains/near/resolver.ts +971 -0
- package/src/chains/near/rpc.ts +1016 -0
- package/src/chains/near/stealth.ts +419 -0
- package/src/chains/near/types.ts +317 -0
- package/src/chains/near/viewing-key.ts +876 -0
- package/src/chains/solana/anchor-transfer.ts +386 -0
- package/src/chains/solana/commitment.ts +577 -0
- package/src/chains/solana/constants.ts +126 -12
- package/src/chains/solana/ephemeral-keys.ts +543 -0
- package/src/chains/solana/index.ts +276 -1
- package/src/chains/solana/key-derivation.ts +418 -0
- package/src/chains/solana/kit-compat.ts +334 -0
- package/src/chains/solana/optimizations.ts +560 -0
- package/src/chains/solana/privacy-adapter.ts +605 -0
- package/src/chains/solana/providers/generic.ts +201 -0
- package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
- package/src/chains/solana/providers/helius-enhanced.ts +623 -0
- package/src/chains/solana/providers/helius.ts +402 -0
- package/src/chains/solana/providers/index.ts +85 -0
- package/src/chains/solana/providers/interface.ts +221 -0
- package/src/chains/solana/providers/quicknode.ts +409 -0
- package/src/chains/solana/providers/triton.ts +426 -0
- package/src/chains/solana/providers/webhook.ts +790 -0
- package/src/chains/solana/rpc-client.ts +1150 -0
- package/src/chains/solana/scan.ts +170 -73
- package/src/chains/solana/sol-transfer.ts +732 -0
- package/src/chains/solana/spl-transfer.ts +886 -0
- package/src/chains/solana/stealth-scanner.ts +703 -0
- package/src/chains/solana/sunspot-verifier.ts +453 -0
- package/src/chains/solana/transaction-builder.ts +755 -0
- package/src/chains/solana/transfer.ts +74 -5
- package/src/chains/solana/types.ts +77 -7
- package/src/chains/solana/utils.ts +110 -0
- package/src/chains/solana/viewing-key.ts +807 -0
- package/src/compliance/fireblocks.ts +921 -0
- package/src/compliance/index.ts +37 -0
- package/src/compliance/range-sas.ts +956 -0
- package/src/config/endpoints.ts +100 -0
- package/src/crypto.ts +11 -8
- package/src/errors.ts +82 -0
- package/src/evm/erc4337-relayer.ts +830 -0
- package/src/evm/index.ts +47 -0
- package/src/fees/calculator.ts +396 -0
- package/src/fees/index.ts +87 -0
- package/src/fees/near-contract.ts +429 -0
- package/src/fees/types.ts +268 -0
- package/src/index.ts +785 -1
- package/src/intent.ts +6 -3
- package/src/logger.ts +324 -0
- package/src/network/index.ts +80 -0
- package/src/network/proxy.ts +691 -0
- package/src/optimizations/index.ts +541 -0
- package/src/oracle/types.ts +1 -0
- package/src/privacy-backends/arcium-types.ts +727 -0
- package/src/privacy-backends/arcium.ts +719 -0
- package/src/privacy-backends/combined-privacy.ts +866 -0
- package/src/privacy-backends/cspl-token.ts +595 -0
- package/src/privacy-backends/cspl-types.ts +512 -0
- package/src/privacy-backends/cspl.ts +907 -0
- package/src/privacy-backends/health.ts +488 -0
- package/src/privacy-backends/inco-types.ts +323 -0
- package/src/privacy-backends/inco.ts +616 -0
- package/src/privacy-backends/index.ts +336 -0
- package/src/privacy-backends/interface.ts +906 -0
- package/src/privacy-backends/lru-cache.ts +343 -0
- package/src/privacy-backends/magicblock.ts +458 -0
- package/src/privacy-backends/mock.ts +258 -0
- package/src/privacy-backends/privacycash-types.ts +278 -0
- package/src/privacy-backends/privacycash.ts +456 -0
- package/src/privacy-backends/private-swap.ts +570 -0
- package/src/privacy-backends/rate-limiter.ts +683 -0
- package/src/privacy-backends/registry.ts +690 -0
- package/src/privacy-backends/router.ts +626 -0
- package/src/privacy-backends/shadowwire.ts +449 -0
- package/src/privacy-backends/sip-native.ts +256 -0
- package/src/privacy-logger.ts +191 -0
- package/src/production-safety.ts +373 -0
- package/src/proofs/aggregator.ts +1029 -0
- package/src/proofs/browser-composer.ts +1150 -0
- package/src/proofs/browser.ts +113 -25
- package/src/proofs/cache/index.ts +127 -0
- package/src/proofs/cache/interface.ts +545 -0
- package/src/proofs/cache/key-generator.ts +188 -0
- package/src/proofs/cache/lru-cache.ts +481 -0
- package/src/proofs/cache/multi-tier-cache.ts +575 -0
- package/src/proofs/cache/persistent-cache.ts +788 -0
- package/src/proofs/compliance-proof.ts +872 -0
- package/src/proofs/composer/base.ts +923 -0
- package/src/proofs/composer/index.ts +25 -0
- package/src/proofs/composer/interface.ts +518 -0
- package/src/proofs/composer/types.ts +383 -0
- package/src/proofs/converters/halo2.ts +452 -0
- package/src/proofs/converters/index.ts +208 -0
- package/src/proofs/converters/interface.ts +363 -0
- package/src/proofs/converters/kimchi.ts +462 -0
- package/src/proofs/converters/noir.ts +451 -0
- package/src/proofs/fallback.ts +888 -0
- package/src/proofs/halo2.ts +42 -0
- package/src/proofs/index.ts +471 -0
- package/src/proofs/interface.ts +13 -0
- package/src/proofs/kimchi.ts +42 -0
- package/src/proofs/lazy.ts +1004 -0
- package/src/proofs/mock.ts +25 -1
- package/src/proofs/noir.ts +111 -30
- package/src/proofs/orchestrator.ts +960 -0
- package/src/proofs/parallel/concurrency.ts +297 -0
- package/src/proofs/parallel/dependency-graph.ts +602 -0
- package/src/proofs/parallel/executor.ts +420 -0
- package/src/proofs/parallel/index.ts +131 -0
- package/src/proofs/parallel/interface.ts +685 -0
- package/src/proofs/parallel/worker-pool.ts +644 -0
- package/src/proofs/providers/halo2.ts +560 -0
- package/src/proofs/providers/index.ts +34 -0
- package/src/proofs/providers/kimchi.ts +641 -0
- package/src/proofs/validator.ts +881 -0
- package/src/proofs/verifier.ts +867 -0
- package/src/quantum/index.ts +112 -0
- package/src/quantum/winternitz-vault.ts +639 -0
- package/src/quantum/wots.ts +611 -0
- package/src/settlement/backends/direct-chain.ts +1 -0
- package/src/settlement/index.ts +9 -0
- package/src/settlement/router.ts +732 -46
- package/src/solana/index.ts +72 -0
- package/src/solana/jito-relayer.ts +687 -0
- package/src/solana/noir-verifier-types.ts +430 -0
- package/src/solana/noir-verifier.ts +816 -0
- package/src/stealth/address-derivation.ts +193 -0
- package/src/stealth/ed25519.ts +431 -0
- package/src/stealth/index.ts +233 -0
- package/src/stealth/meta-address.ts +221 -0
- package/src/stealth/secp256k1.ts +368 -0
- package/src/stealth/utils.ts +194 -0
- package/src/stealth.ts +50 -1504
- package/src/surveillance/algorithms/address-reuse.ts +143 -0
- package/src/surveillance/algorithms/cluster.ts +247 -0
- package/src/surveillance/algorithms/exchange.ts +295 -0
- package/src/surveillance/algorithms/temporal.ts +337 -0
- package/src/surveillance/analyzer.ts +442 -0
- package/src/surveillance/index.ts +64 -0
- package/src/surveillance/scoring.ts +372 -0
- package/src/surveillance/types.ts +264 -0
- package/src/sync/index.ts +106 -0
- package/src/sync/manager.ts +504 -0
- package/src/sync/mock-provider.ts +318 -0
- package/src/sync/oblivious.ts +625 -0
- package/src/tokens/index.ts +15 -0
- package/src/tokens/registry.ts +301 -0
- package/src/utils/deprecation.ts +94 -0
- package/src/utils/index.ts +9 -0
- package/src/wallet/ethereum/index.ts +68 -0
- package/src/wallet/ethereum/metamask-privacy.ts +420 -0
- package/src/wallet/ethereum/multi-wallet.ts +646 -0
- package/src/wallet/ethereum/privacy-adapter.ts +700 -0
- package/src/wallet/ethereum/types.ts +3 -1
- package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
- package/src/wallet/hardware/index.ts +10 -0
- package/src/wallet/hardware/ledger-privacy.ts +414 -0
- package/src/wallet/index.ts +71 -0
- package/src/wallet/near/adapter.ts +626 -0
- package/src/wallet/near/index.ts +86 -0
- package/src/wallet/near/meteor-wallet.ts +1153 -0
- package/src/wallet/near/my-near-wallet.ts +790 -0
- package/src/wallet/near/wallet-selector.ts +702 -0
- package/src/wallet/solana/adapter.ts +6 -4
- package/src/wallet/solana/index.ts +13 -0
- package/src/wallet/solana/privacy-adapter.ts +567 -0
- package/src/wallet/sui/types.ts +6 -4
- package/src/zcash/rpc-client.ts +13 -6
- package/dist/chunk-3INS3PR5.mjs +0 -884
- package/dist/chunk-3OVABDRH.mjs +0 -17096
- package/dist/chunk-DLDWZFYC.mjs +0 -1495
- package/dist/chunk-E6SZWREQ.mjs +0 -57
- package/dist/chunk-G33LB27A.mjs +0 -16166
- package/dist/chunk-HGU6HZRC.mjs +0 -231
- package/dist/chunk-L2K34JCU.mjs +0 -1496
- package/dist/chunk-SN4ZDTVW.mjs +0 -16166
- package/dist/constants-VOI7BSLK.mjs +0 -27
- package/dist/index-BYZbDjal.d.ts +0 -11390
- package/dist/index-CHB3KuOB.d.mts +0 -11859
- package/dist/index-CzWPI6Le.d.ts +0 -11859
- package/dist/index-xbWjohNq.d.mts +0 -11390
- package/dist/solana-5EMCTPTS.mjs +0 -46
- package/dist/solana-Q4NAVBTS.mjs +0 -46
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helius Webhook Handler
|
|
3
|
+
*
|
|
4
|
+
* Real-time stealth payment detection using Helius webhooks.
|
|
5
|
+
* Push-based notifications instead of polling for efficient scanning.
|
|
6
|
+
*
|
|
7
|
+
* @see https://docs.helius.dev/webhooks-and-websockets/webhooks
|
|
8
|
+
*
|
|
9
|
+
* @example Server setup (Express.js)
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import express from 'express'
|
|
12
|
+
* import { createWebhookHandler } from '@sip-protocol/sdk'
|
|
13
|
+
*
|
|
14
|
+
* const app = express()
|
|
15
|
+
* app.use(express.json())
|
|
16
|
+
*
|
|
17
|
+
* const handler = createWebhookHandler({
|
|
18
|
+
* viewingPrivateKey: '0x...',
|
|
19
|
+
* spendingPublicKey: '0x...',
|
|
20
|
+
* onPaymentFound: (payment) => {
|
|
21
|
+
* console.log('Found payment!', payment)
|
|
22
|
+
* // Notify user, update database, etc.
|
|
23
|
+
* },
|
|
24
|
+
* })
|
|
25
|
+
*
|
|
26
|
+
* app.post('/webhook/helius', async (req, res) => {
|
|
27
|
+
* await handler(req.body)
|
|
28
|
+
* res.status(200).send('OK')
|
|
29
|
+
* })
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example Helius webhook configuration
|
|
33
|
+
* ```
|
|
34
|
+
* Webhook URL: https://your-server.com/webhook/helius
|
|
35
|
+
* Transaction Type: Any (or TRANSFER for token transfers)
|
|
36
|
+
* Account Addresses: [MEMO_PROGRAM_ID] for memo filtering
|
|
37
|
+
* Webhook Type: raw (to get full transaction data)
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import type { HexString } from '@sip-protocol/types'
|
|
42
|
+
import {
|
|
43
|
+
checkEd25519StealthAddress,
|
|
44
|
+
solanaAddressToEd25519PublicKey,
|
|
45
|
+
} from '../../../stealth'
|
|
46
|
+
import type { StealthAddress } from '@sip-protocol/types'
|
|
47
|
+
import { parseAnnouncement } from '../types'
|
|
48
|
+
import type { SolanaScanResult } from '../types'
|
|
49
|
+
import { SIP_MEMO_PREFIX } from '../constants'
|
|
50
|
+
import { getTokenSymbol, parseTokenTransferFromBalances } from '../utils'
|
|
51
|
+
import { ValidationError, SecurityError } from '../../../errors'
|
|
52
|
+
import { hmac } from '@noble/hashes/hmac'
|
|
53
|
+
import { sha256 } from '@noble/hashes/sha256'
|
|
54
|
+
import { bytesToHex } from '@noble/hashes/utils'
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Helius raw webhook payload for a transaction
|
|
58
|
+
*
|
|
59
|
+
* @see https://docs.helius.dev/webhooks-and-websockets/webhooks
|
|
60
|
+
*/
|
|
61
|
+
export interface HeliusWebhookTransaction {
|
|
62
|
+
/** Block timestamp (Unix seconds) */
|
|
63
|
+
blockTime: number
|
|
64
|
+
/** Position within block */
|
|
65
|
+
indexWithinBlock?: number
|
|
66
|
+
/** Transaction metadata */
|
|
67
|
+
meta: {
|
|
68
|
+
/** Error if transaction failed */
|
|
69
|
+
err: unknown | null
|
|
70
|
+
/** Transaction fee in lamports */
|
|
71
|
+
fee: number
|
|
72
|
+
/** Inner instructions (CPI calls) */
|
|
73
|
+
innerInstructions: Array<{
|
|
74
|
+
index: number
|
|
75
|
+
instructions: Array<{
|
|
76
|
+
accounts: number[]
|
|
77
|
+
data: string
|
|
78
|
+
programIdIndex: number
|
|
79
|
+
}>
|
|
80
|
+
}>
|
|
81
|
+
/** Loaded address tables */
|
|
82
|
+
loadedAddresses?: {
|
|
83
|
+
readonly: string[]
|
|
84
|
+
writable: string[]
|
|
85
|
+
}
|
|
86
|
+
/** Program log messages */
|
|
87
|
+
logMessages: string[]
|
|
88
|
+
/** Post-transaction lamport balances */
|
|
89
|
+
postBalances: number[]
|
|
90
|
+
/** Post-transaction token balances */
|
|
91
|
+
postTokenBalances: Array<{
|
|
92
|
+
accountIndex: number
|
|
93
|
+
mint: string
|
|
94
|
+
owner?: string
|
|
95
|
+
programId?: string
|
|
96
|
+
uiTokenAmount: {
|
|
97
|
+
amount: string
|
|
98
|
+
decimals: number
|
|
99
|
+
uiAmount: number | null
|
|
100
|
+
uiAmountString: string
|
|
101
|
+
}
|
|
102
|
+
}>
|
|
103
|
+
/** Pre-transaction lamport balances */
|
|
104
|
+
preBalances: number[]
|
|
105
|
+
/** Pre-transaction token balances */
|
|
106
|
+
preTokenBalances: Array<{
|
|
107
|
+
accountIndex: number
|
|
108
|
+
mint: string
|
|
109
|
+
owner?: string
|
|
110
|
+
programId?: string
|
|
111
|
+
uiTokenAmount: {
|
|
112
|
+
amount: string
|
|
113
|
+
decimals: number
|
|
114
|
+
uiAmount: number | null
|
|
115
|
+
uiAmountString: string
|
|
116
|
+
}
|
|
117
|
+
}>
|
|
118
|
+
/** Rewards */
|
|
119
|
+
rewards: unknown[]
|
|
120
|
+
}
|
|
121
|
+
/** Slot number */
|
|
122
|
+
slot: number
|
|
123
|
+
/** Transaction data */
|
|
124
|
+
transaction: {
|
|
125
|
+
/** Transaction message */
|
|
126
|
+
message: {
|
|
127
|
+
/** Account keys involved */
|
|
128
|
+
accountKeys: string[]
|
|
129
|
+
/** Compiled instructions */
|
|
130
|
+
instructions: Array<{
|
|
131
|
+
accounts: number[]
|
|
132
|
+
data: string
|
|
133
|
+
programIdIndex: number
|
|
134
|
+
}>
|
|
135
|
+
/** Recent blockhash */
|
|
136
|
+
recentBlockhash: string
|
|
137
|
+
}
|
|
138
|
+
/** Transaction signatures */
|
|
139
|
+
signatures: string[]
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Helius enhanced webhook payload
|
|
145
|
+
*
|
|
146
|
+
* Enhanced webhooks provide parsed/decoded transaction data.
|
|
147
|
+
*/
|
|
148
|
+
export interface HeliusEnhancedTransaction {
|
|
149
|
+
/** Human-readable description */
|
|
150
|
+
description: string
|
|
151
|
+
/** Transaction type (TRANSFER, NFT_SALE, etc.) */
|
|
152
|
+
type: string
|
|
153
|
+
/** Source wallet/program */
|
|
154
|
+
source: string
|
|
155
|
+
/** Transaction fee in lamports */
|
|
156
|
+
fee: number
|
|
157
|
+
/** Fee payer address */
|
|
158
|
+
feePayer: string
|
|
159
|
+
/** Transaction signature */
|
|
160
|
+
signature: string
|
|
161
|
+
/** Slot number */
|
|
162
|
+
slot: number
|
|
163
|
+
/** Block timestamp */
|
|
164
|
+
timestamp: number
|
|
165
|
+
/** Native SOL transfers */
|
|
166
|
+
nativeTransfers: Array<{
|
|
167
|
+
fromUserAccount: string
|
|
168
|
+
toUserAccount: string
|
|
169
|
+
amount: number
|
|
170
|
+
}>
|
|
171
|
+
/** Token transfers */
|
|
172
|
+
tokenTransfers: Array<{
|
|
173
|
+
fromUserAccount: string
|
|
174
|
+
toUserAccount: string
|
|
175
|
+
fromTokenAccount: string
|
|
176
|
+
toTokenAccount: string
|
|
177
|
+
tokenAmount: number
|
|
178
|
+
mint: string
|
|
179
|
+
tokenStandard: string
|
|
180
|
+
}>
|
|
181
|
+
/** Account data changes */
|
|
182
|
+
accountData: Array<{
|
|
183
|
+
account: string
|
|
184
|
+
nativeBalanceChange: number
|
|
185
|
+
tokenBalanceChanges: Array<{
|
|
186
|
+
userAccount: string
|
|
187
|
+
tokenAccount: string
|
|
188
|
+
mint: string
|
|
189
|
+
rawTokenAmount: {
|
|
190
|
+
tokenAmount: string
|
|
191
|
+
decimals: number
|
|
192
|
+
}
|
|
193
|
+
}>
|
|
194
|
+
}>
|
|
195
|
+
/** Events (NFT sales, etc.) */
|
|
196
|
+
events?: Record<string, unknown>
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Webhook payload (can be single transaction or array)
|
|
201
|
+
*/
|
|
202
|
+
export type HeliusWebhookPayload =
|
|
203
|
+
| HeliusWebhookTransaction
|
|
204
|
+
| HeliusWebhookTransaction[]
|
|
205
|
+
| HeliusEnhancedTransaction
|
|
206
|
+
| HeliusEnhancedTransaction[]
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Configuration for the Helius webhook handler
|
|
210
|
+
*
|
|
211
|
+
* @security This config contains sensitive cryptographic keys.
|
|
212
|
+
* Never log, store in plain text, or transmit insecurely.
|
|
213
|
+
*/
|
|
214
|
+
export interface WebhookHandlerConfig {
|
|
215
|
+
/**
|
|
216
|
+
* Recipient's viewing private key (hex)
|
|
217
|
+
*
|
|
218
|
+
* @security SENSITIVE - This key enables detection of incoming payments.
|
|
219
|
+
* Store securely (encrypted). Never log or expose in error messages.
|
|
220
|
+
* Use environment variables or secure vault in production.
|
|
221
|
+
*/
|
|
222
|
+
viewingPrivateKey: HexString
|
|
223
|
+
/** Recipient's spending public key (hex) */
|
|
224
|
+
spendingPublicKey: HexString
|
|
225
|
+
/**
|
|
226
|
+
* Callback when a payment is found
|
|
227
|
+
*
|
|
228
|
+
* @param payment - The detected payment details
|
|
229
|
+
*/
|
|
230
|
+
onPaymentFound: (payment: SolanaScanResult) => void | Promise<void>
|
|
231
|
+
/**
|
|
232
|
+
* Optional callback for errors
|
|
233
|
+
*
|
|
234
|
+
* @param error - The error that occurred
|
|
235
|
+
* @param transaction - The transaction that caused the error (if available)
|
|
236
|
+
*/
|
|
237
|
+
onError?: (error: Error, transaction?: HeliusWebhookTransaction) => void
|
|
238
|
+
/**
|
|
239
|
+
* Webhook authentication secret (recommended for production)
|
|
240
|
+
*
|
|
241
|
+
* When set, the handler will verify the X-Helius-Signature header
|
|
242
|
+
* using HMAC-SHA256 to ensure webhook payloads are authentic.
|
|
243
|
+
*
|
|
244
|
+
* Get your webhook secret from Helius Dashboard:
|
|
245
|
+
* https://dev.helius.xyz/webhooks
|
|
246
|
+
*/
|
|
247
|
+
webhookSecret?: string
|
|
248
|
+
/**
|
|
249
|
+
* Authorization token for additional security
|
|
250
|
+
*
|
|
251
|
+
* When set, the handler will verify the Authorization header
|
|
252
|
+
* matches this token. Use for simple auth in trusted environments.
|
|
253
|
+
*/
|
|
254
|
+
authToken?: string
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Webhook request with headers for signature verification
|
|
259
|
+
*/
|
|
260
|
+
export interface WebhookRequest {
|
|
261
|
+
/** Raw request body as string (for signature verification) */
|
|
262
|
+
rawBody: string
|
|
263
|
+
/** Parsed payload */
|
|
264
|
+
payload: HeliusWebhookPayload
|
|
265
|
+
/** Request headers */
|
|
266
|
+
headers: {
|
|
267
|
+
/** Helius webhook signature (X-Helius-Signature header) */
|
|
268
|
+
signature?: string
|
|
269
|
+
/** Authorization header */
|
|
270
|
+
authorization?: string
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Result of processing a single webhook transaction
|
|
276
|
+
*/
|
|
277
|
+
export interface WebhookProcessResult {
|
|
278
|
+
/** Whether a payment was found for us */
|
|
279
|
+
found: boolean
|
|
280
|
+
/** The payment details (if found) */
|
|
281
|
+
payment?: SolanaScanResult
|
|
282
|
+
/** Transaction signature */
|
|
283
|
+
signature: string
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Verify Helius webhook signature using HMAC-SHA256
|
|
288
|
+
*
|
|
289
|
+
* H-4, H-8 FIX: Implement webhook signature verification
|
|
290
|
+
*
|
|
291
|
+
* @param rawBody - Raw request body as string
|
|
292
|
+
* @param signature - Signature from X-Helius-Signature header
|
|
293
|
+
* @param secret - Webhook secret from Helius dashboard
|
|
294
|
+
* @returns True if signature is valid
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* ```typescript
|
|
298
|
+
* const isValid = verifyWebhookSignature(
|
|
299
|
+
* req.rawBody,
|
|
300
|
+
* req.headers['x-helius-signature'],
|
|
301
|
+
* process.env.HELIUS_WEBHOOK_SECRET!
|
|
302
|
+
* )
|
|
303
|
+
* if (!isValid) {
|
|
304
|
+
* res.status(401).send('Invalid signature')
|
|
305
|
+
* return
|
|
306
|
+
* }
|
|
307
|
+
* ```
|
|
308
|
+
*/
|
|
309
|
+
export function verifyWebhookSignature(
|
|
310
|
+
rawBody: string,
|
|
311
|
+
signature: string | undefined,
|
|
312
|
+
secret: string
|
|
313
|
+
): boolean {
|
|
314
|
+
if (!signature || !secret) {
|
|
315
|
+
return false
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
// Compute expected signature using HMAC-SHA256
|
|
320
|
+
const encoder = new TextEncoder()
|
|
321
|
+
const expectedSignature = bytesToHex(
|
|
322
|
+
hmac(sha256, encoder.encode(secret), encoder.encode(rawBody))
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
// Constant-time comparison to prevent timing attacks
|
|
326
|
+
if (signature.length !== expectedSignature.length) {
|
|
327
|
+
return false
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
let result = 0
|
|
331
|
+
for (let i = 0; i < signature.length; i++) {
|
|
332
|
+
result |= signature.charCodeAt(i) ^ expectedSignature.charCodeAt(i)
|
|
333
|
+
}
|
|
334
|
+
return result === 0
|
|
335
|
+
} catch {
|
|
336
|
+
return false
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Verify authorization token
|
|
342
|
+
*
|
|
343
|
+
* H-7 FIX: Implement authorization verification
|
|
344
|
+
*
|
|
345
|
+
* @param authHeader - Authorization header value
|
|
346
|
+
* @param expectedToken - Expected token
|
|
347
|
+
* @returns True if token matches
|
|
348
|
+
*/
|
|
349
|
+
export function verifyAuthToken(
|
|
350
|
+
authHeader: string | undefined,
|
|
351
|
+
expectedToken: string
|
|
352
|
+
): boolean {
|
|
353
|
+
if (!authHeader || !expectedToken) {
|
|
354
|
+
return false
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Support both "Bearer <token>" and raw token formats
|
|
358
|
+
const token = authHeader.startsWith('Bearer ')
|
|
359
|
+
? authHeader.slice(7)
|
|
360
|
+
: authHeader
|
|
361
|
+
|
|
362
|
+
// Constant-time comparison
|
|
363
|
+
if (token.length !== expectedToken.length) {
|
|
364
|
+
return false
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
let result = 0
|
|
368
|
+
for (let i = 0; i < token.length; i++) {
|
|
369
|
+
result |= token.charCodeAt(i) ^ expectedToken.charCodeAt(i)
|
|
370
|
+
}
|
|
371
|
+
return result === 0
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Handler function type returned by createWebhookHandler
|
|
376
|
+
*/
|
|
377
|
+
export interface WebhookHandler {
|
|
378
|
+
/**
|
|
379
|
+
* Process a webhook payload (simple mode - no signature verification)
|
|
380
|
+
*/
|
|
381
|
+
(payload: HeliusWebhookPayload): Promise<WebhookProcessResult[]>
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Process a webhook request with full authentication
|
|
385
|
+
*
|
|
386
|
+
* H-4, H-7, H-8 FIX: Supports signature and auth token verification
|
|
387
|
+
*/
|
|
388
|
+
processRequest(request: WebhookRequest): Promise<WebhookProcessResult[]>
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Create a webhook handler for processing Helius webhook payloads
|
|
393
|
+
*
|
|
394
|
+
* @param config - Handler configuration
|
|
395
|
+
* @returns Handler function to process webhook payloads
|
|
396
|
+
*
|
|
397
|
+
* @example Simple usage (not recommended for production)
|
|
398
|
+
* ```typescript
|
|
399
|
+
* const handler = createWebhookHandler({
|
|
400
|
+
* viewingPrivateKey: recipientKeys.viewingPrivateKey,
|
|
401
|
+
* spendingPublicKey: recipientKeys.spendingPublicKey,
|
|
402
|
+
* onPaymentFound: async (payment) => {
|
|
403
|
+
* await db.savePayment(payment)
|
|
404
|
+
* },
|
|
405
|
+
* })
|
|
406
|
+
*
|
|
407
|
+
* app.post('/webhook', async (req, res) => {
|
|
408
|
+
* const results = await handler(req.body)
|
|
409
|
+
* res.json({ processed: results.length })
|
|
410
|
+
* })
|
|
411
|
+
* ```
|
|
412
|
+
*
|
|
413
|
+
* @example Production usage with signature verification
|
|
414
|
+
* ```typescript
|
|
415
|
+
* const handler = createWebhookHandler({
|
|
416
|
+
* viewingPrivateKey: recipientKeys.viewingPrivateKey,
|
|
417
|
+
* spendingPublicKey: recipientKeys.spendingPublicKey,
|
|
418
|
+
* webhookSecret: process.env.HELIUS_WEBHOOK_SECRET!,
|
|
419
|
+
* onPaymentFound: async (payment) => {
|
|
420
|
+
* await db.savePayment(payment)
|
|
421
|
+
* },
|
|
422
|
+
* })
|
|
423
|
+
*
|
|
424
|
+
* app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
|
|
425
|
+
* try {
|
|
426
|
+
* const results = await handler.processRequest({
|
|
427
|
+
* rawBody: req.body.toString(),
|
|
428
|
+
* payload: JSON.parse(req.body.toString()),
|
|
429
|
+
* headers: {
|
|
430
|
+
* signature: req.headers['x-helius-signature'] as string,
|
|
431
|
+
* authorization: req.headers['authorization'] as string,
|
|
432
|
+
* },
|
|
433
|
+
* })
|
|
434
|
+
* res.json({ processed: results.length })
|
|
435
|
+
* } catch (error) {
|
|
436
|
+
* if (error instanceof SecurityError) {
|
|
437
|
+
* res.status(401).json({ error: error.message })
|
|
438
|
+
* } else {
|
|
439
|
+
* res.status(500).json({ error: 'Internal error' })
|
|
440
|
+
* }
|
|
441
|
+
* }
|
|
442
|
+
* })
|
|
443
|
+
* ```
|
|
444
|
+
*/
|
|
445
|
+
export function createWebhookHandler(config: WebhookHandlerConfig): WebhookHandler {
|
|
446
|
+
const {
|
|
447
|
+
viewingPrivateKey,
|
|
448
|
+
spendingPublicKey,
|
|
449
|
+
onPaymentFound,
|
|
450
|
+
onError,
|
|
451
|
+
webhookSecret,
|
|
452
|
+
authToken,
|
|
453
|
+
} = config
|
|
454
|
+
|
|
455
|
+
// Validate required keys
|
|
456
|
+
if (!viewingPrivateKey || !viewingPrivateKey.startsWith('0x')) {
|
|
457
|
+
throw new ValidationError('viewingPrivateKey must be a valid hex string starting with 0x', 'viewingPrivateKey')
|
|
458
|
+
}
|
|
459
|
+
if (!spendingPublicKey || !spendingPublicKey.startsWith('0x')) {
|
|
460
|
+
throw new ValidationError('spendingPublicKey must be a valid hex string starting with 0x', 'spendingPublicKey')
|
|
461
|
+
}
|
|
462
|
+
if (typeof onPaymentFound !== 'function') {
|
|
463
|
+
throw new ValidationError('onPaymentFound callback is required', 'onPaymentFound')
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Validate key lengths (ed25519 keys are 32 bytes = 64 hex chars + '0x' prefix)
|
|
467
|
+
if (viewingPrivateKey.length !== 66) {
|
|
468
|
+
throw new ValidationError('viewingPrivateKey must be 32 bytes (64 hex characters)', 'viewingPrivateKey')
|
|
469
|
+
}
|
|
470
|
+
if (spendingPublicKey.length !== 66) {
|
|
471
|
+
throw new ValidationError('spendingPublicKey must be 32 bytes (64 hex characters)', 'spendingPublicKey')
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Process transactions after authentication
|
|
476
|
+
*/
|
|
477
|
+
async function processTransactions(
|
|
478
|
+
payload: HeliusWebhookPayload
|
|
479
|
+
): Promise<WebhookProcessResult[]> {
|
|
480
|
+
// H-4 FIX: Validate payload structure before processing
|
|
481
|
+
if (!payload || (typeof payload !== 'object')) {
|
|
482
|
+
throw new ValidationError('Invalid webhook payload', 'payload')
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Normalize to array
|
|
486
|
+
const transactions = Array.isArray(payload) ? payload : [payload]
|
|
487
|
+
|
|
488
|
+
// Validate we have at least one transaction
|
|
489
|
+
if (transactions.length === 0) {
|
|
490
|
+
return []
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Limit batch size to prevent DoS
|
|
494
|
+
const MAX_BATCH_SIZE = 100
|
|
495
|
+
if (transactions.length > MAX_BATCH_SIZE) {
|
|
496
|
+
throw new ValidationError(
|
|
497
|
+
`Batch size exceeds maximum of ${MAX_BATCH_SIZE}`,
|
|
498
|
+
'payload'
|
|
499
|
+
)
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const results: WebhookProcessResult[] = []
|
|
503
|
+
|
|
504
|
+
for (const tx of transactions) {
|
|
505
|
+
try {
|
|
506
|
+
// Handle raw vs enhanced webhook format
|
|
507
|
+
if (isRawTransaction(tx)) {
|
|
508
|
+
const result = await processRawTransaction(
|
|
509
|
+
tx,
|
|
510
|
+
viewingPrivateKey,
|
|
511
|
+
spendingPublicKey,
|
|
512
|
+
onPaymentFound
|
|
513
|
+
)
|
|
514
|
+
results.push(result)
|
|
515
|
+
} else {
|
|
516
|
+
// Enhanced transactions don't include log messages,
|
|
517
|
+
// so we can't detect SIP announcements directly
|
|
518
|
+
// For now, skip enhanced transactions
|
|
519
|
+
results.push({
|
|
520
|
+
found: false,
|
|
521
|
+
signature: (tx as HeliusEnhancedTransaction).signature,
|
|
522
|
+
})
|
|
523
|
+
}
|
|
524
|
+
} catch (error) {
|
|
525
|
+
onError?.(error as Error, isRawTransaction(tx) ? tx : undefined)
|
|
526
|
+
results.push({
|
|
527
|
+
found: false,
|
|
528
|
+
signature: getSignature(tx),
|
|
529
|
+
})
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return results
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Simple handler (backwards compatible)
|
|
538
|
+
*/
|
|
539
|
+
const handler = async (payload: HeliusWebhookPayload): Promise<WebhookProcessResult[]> => {
|
|
540
|
+
return processTransactions(payload)
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Process request with full authentication
|
|
545
|
+
* H-4, H-7, H-8 FIX: Signature and auth verification
|
|
546
|
+
*/
|
|
547
|
+
handler.processRequest = async (request: WebhookRequest): Promise<WebhookProcessResult[]> => {
|
|
548
|
+
// Verify webhook signature if configured
|
|
549
|
+
if (webhookSecret) {
|
|
550
|
+
if (!verifyWebhookSignature(request.rawBody, request.headers.signature, webhookSecret)) {
|
|
551
|
+
throw new SecurityError(
|
|
552
|
+
'Invalid webhook signature - request rejected',
|
|
553
|
+
'INVALID_SIGNATURE'
|
|
554
|
+
)
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Verify auth token if configured
|
|
559
|
+
if (authToken) {
|
|
560
|
+
if (!verifyAuthToken(request.headers.authorization, authToken)) {
|
|
561
|
+
throw new SecurityError(
|
|
562
|
+
'Invalid authorization token - request rejected',
|
|
563
|
+
'INVALID_AUTH'
|
|
564
|
+
)
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return processTransactions(request.payload)
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return handler as WebhookHandler
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Process a single raw transaction for SIP announcements
|
|
576
|
+
*/
|
|
577
|
+
async function processRawTransaction(
|
|
578
|
+
tx: HeliusWebhookTransaction,
|
|
579
|
+
viewingPrivateKey: HexString,
|
|
580
|
+
spendingPublicKey: HexString,
|
|
581
|
+
onPaymentFound: (payment: SolanaScanResult) => void | Promise<void>
|
|
582
|
+
): Promise<WebhookProcessResult> {
|
|
583
|
+
const signature = tx.transaction?.signatures?.[0] ?? 'unknown'
|
|
584
|
+
|
|
585
|
+
// Check if transaction failed
|
|
586
|
+
if (tx.meta?.err) {
|
|
587
|
+
return { found: false, signature }
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Ensure log messages exist
|
|
591
|
+
if (!tx.meta?.logMessages) {
|
|
592
|
+
return { found: false, signature }
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Search log messages for SIP announcement
|
|
596
|
+
for (const log of tx.meta.logMessages) {
|
|
597
|
+
if (!log.includes(SIP_MEMO_PREFIX)) continue
|
|
598
|
+
|
|
599
|
+
// Extract memo content from log
|
|
600
|
+
const memoMatch = log.match(/Program log: (.+)/)
|
|
601
|
+
if (!memoMatch) continue
|
|
602
|
+
|
|
603
|
+
const memoContent = memoMatch[1]
|
|
604
|
+
const announcement = parseAnnouncement(memoContent)
|
|
605
|
+
if (!announcement) continue
|
|
606
|
+
|
|
607
|
+
// Check if this payment is for us
|
|
608
|
+
// H-5 FIX: Validate ephemeral public key before curve operations
|
|
609
|
+
if (!announcement.ephemeralPublicKey || announcement.ephemeralPublicKey.length < 32) {
|
|
610
|
+
continue // Invalid ephemeral key format, skip
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
let ephemeralPubKeyHex: HexString
|
|
614
|
+
try {
|
|
615
|
+
ephemeralPubKeyHex = solanaAddressToEd25519PublicKey(
|
|
616
|
+
announcement.ephemeralPublicKey
|
|
617
|
+
)
|
|
618
|
+
} catch {
|
|
619
|
+
// Invalid base58 encoding or malformed key
|
|
620
|
+
continue
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// H-3 FIX: Enhanced view tag validation with strict hex parsing
|
|
624
|
+
// View tags are 1 byte (0-255), represented as 2-char hex string
|
|
625
|
+
const viewTagStr = announcement.viewTag
|
|
626
|
+
if (!viewTagStr || typeof viewTagStr !== 'string') {
|
|
627
|
+
continue
|
|
628
|
+
}
|
|
629
|
+
// Validate hex format (only 0-9, a-f, A-F)
|
|
630
|
+
if (!/^[0-9a-fA-F]{1,2}$/.test(viewTagStr)) {
|
|
631
|
+
continue // Invalid hex format
|
|
632
|
+
}
|
|
633
|
+
const viewTagNumber = parseInt(viewTagStr, 16)
|
|
634
|
+
// Double-check bounds after parse (defense in depth)
|
|
635
|
+
if (!Number.isInteger(viewTagNumber) || viewTagNumber < 0 || viewTagNumber > 255) {
|
|
636
|
+
continue // Invalid view tag, skip this announcement
|
|
637
|
+
}
|
|
638
|
+
const stealthAddressToCheck: StealthAddress = {
|
|
639
|
+
address: announcement.stealthAddress
|
|
640
|
+
? solanaAddressToEd25519PublicKey(announcement.stealthAddress)
|
|
641
|
+
: ('0x' + '00'.repeat(32)) as HexString,
|
|
642
|
+
ephemeralPublicKey: ephemeralPubKeyHex,
|
|
643
|
+
viewTag: viewTagNumber,
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Check if this is our payment (may throw for invalid curve points)
|
|
647
|
+
let isOurs = false
|
|
648
|
+
try {
|
|
649
|
+
isOurs = checkEd25519StealthAddress(
|
|
650
|
+
stealthAddressToCheck,
|
|
651
|
+
viewingPrivateKey,
|
|
652
|
+
spendingPublicKey
|
|
653
|
+
)
|
|
654
|
+
} catch {
|
|
655
|
+
// Invalid ephemeral key or malformed data - not our payment
|
|
656
|
+
continue
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (isOurs) {
|
|
660
|
+
// Parse token transfer info using shared utility
|
|
661
|
+
const transferInfo = parseTokenTransferFromBalances(
|
|
662
|
+
tx.meta.preTokenBalances,
|
|
663
|
+
tx.meta.postTokenBalances
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
const payment: SolanaScanResult = {
|
|
667
|
+
stealthAddress: announcement.stealthAddress || '',
|
|
668
|
+
ephemeralPublicKey: announcement.ephemeralPublicKey,
|
|
669
|
+
amount: transferInfo?.amount ?? 0n,
|
|
670
|
+
mint: transferInfo?.mint ?? '',
|
|
671
|
+
tokenSymbol: transferInfo?.mint ? getTokenSymbol(transferInfo.mint) : undefined,
|
|
672
|
+
txSignature: signature,
|
|
673
|
+
slot: tx.slot,
|
|
674
|
+
timestamp: tx.blockTime,
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Call the callback (wrap in try-catch to prevent callback errors from breaking processing)
|
|
678
|
+
try {
|
|
679
|
+
await onPaymentFound(payment)
|
|
680
|
+
} catch {
|
|
681
|
+
// Callback error should not prevent returning the found payment
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
return { found: true, payment, signature }
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return { found: false, signature }
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Token transfer parsing and symbol lookup moved to ../utils.ts (L3 fix)
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Type guard for raw Helius webhook transaction
|
|
695
|
+
*
|
|
696
|
+
* Distinguishes between raw and enhanced transaction formats.
|
|
697
|
+
* Raw transactions have transaction.signatures array, enhanced have signature directly.
|
|
698
|
+
*
|
|
699
|
+
* @param tx - Unknown transaction payload
|
|
700
|
+
* @returns True if tx is a raw HeliusWebhookTransaction
|
|
701
|
+
* @internal
|
|
702
|
+
*/
|
|
703
|
+
function isRawTransaction(tx: unknown): tx is HeliusWebhookTransaction {
|
|
704
|
+
return (
|
|
705
|
+
typeof tx === 'object' &&
|
|
706
|
+
tx !== null &&
|
|
707
|
+
'meta' in tx &&
|
|
708
|
+
'transaction' in tx &&
|
|
709
|
+
Array.isArray((tx as HeliusWebhookTransaction).transaction?.signatures)
|
|
710
|
+
)
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Type guard for enhanced Helius webhook transaction
|
|
715
|
+
*
|
|
716
|
+
* Distinguishes between raw and enhanced transaction formats.
|
|
717
|
+
* Enhanced transactions have signature at top level with type field.
|
|
718
|
+
*
|
|
719
|
+
* @param tx - Unknown transaction payload
|
|
720
|
+
* @returns True if tx is an enhanced HeliusEnhancedTransaction
|
|
721
|
+
* @internal
|
|
722
|
+
*/
|
|
723
|
+
function isEnhancedTransaction(tx: unknown): tx is HeliusEnhancedTransaction {
|
|
724
|
+
return (
|
|
725
|
+
typeof tx === 'object' &&
|
|
726
|
+
tx !== null &&
|
|
727
|
+
'signature' in tx &&
|
|
728
|
+
'type' in tx &&
|
|
729
|
+
typeof (tx as HeliusEnhancedTransaction).signature === 'string'
|
|
730
|
+
)
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Extract transaction signature from webhook payload
|
|
735
|
+
*
|
|
736
|
+
* Handles both raw and enhanced transaction formats.
|
|
737
|
+
*
|
|
738
|
+
* @param tx - Helius webhook transaction (raw or enhanced)
|
|
739
|
+
* @returns Transaction signature, or 'unknown' if not found
|
|
740
|
+
* @internal
|
|
741
|
+
*/
|
|
742
|
+
function getSignature(tx: HeliusWebhookTransaction | HeliusEnhancedTransaction): string {
|
|
743
|
+
// Enhanced transactions have signature at top level
|
|
744
|
+
if ('signature' in tx && typeof (tx as HeliusEnhancedTransaction).signature === 'string') {
|
|
745
|
+
return (tx as HeliusEnhancedTransaction).signature
|
|
746
|
+
}
|
|
747
|
+
// Raw transactions have signatures array in transaction object
|
|
748
|
+
if (isRawTransaction(tx)) {
|
|
749
|
+
return tx.transaction?.signatures?.[0] ?? 'unknown'
|
|
750
|
+
}
|
|
751
|
+
return 'unknown'
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Process a single webhook transaction and check if it's a payment for us
|
|
756
|
+
*
|
|
757
|
+
* Lower-level function for custom webhook handling.
|
|
758
|
+
*
|
|
759
|
+
* @param transaction - Raw Helius webhook transaction
|
|
760
|
+
* @param viewingPrivateKey - Recipient's viewing private key
|
|
761
|
+
* @param spendingPublicKey - Recipient's spending public key
|
|
762
|
+
* @returns Payment result if found, null otherwise
|
|
763
|
+
*
|
|
764
|
+
* @example
|
|
765
|
+
* ```typescript
|
|
766
|
+
* const payment = await processWebhookTransaction(
|
|
767
|
+
* webhookPayload,
|
|
768
|
+
* viewingPrivateKey,
|
|
769
|
+
* spendingPublicKey
|
|
770
|
+
* )
|
|
771
|
+
*
|
|
772
|
+
* if (payment) {
|
|
773
|
+
* console.log('Found payment:', payment.amount, payment.mint)
|
|
774
|
+
* }
|
|
775
|
+
* ```
|
|
776
|
+
*/
|
|
777
|
+
export async function processWebhookTransaction(
|
|
778
|
+
transaction: HeliusWebhookTransaction,
|
|
779
|
+
viewingPrivateKey: HexString,
|
|
780
|
+
spendingPublicKey: HexString
|
|
781
|
+
): Promise<SolanaScanResult | null> {
|
|
782
|
+
const result = await processRawTransaction(
|
|
783
|
+
transaction,
|
|
784
|
+
viewingPrivateKey,
|
|
785
|
+
spendingPublicKey,
|
|
786
|
+
() => {} // No-op callback
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
return result.found ? result.payment ?? null : null
|
|
790
|
+
}
|