@sip-protocol/sdk 0.8.1 → 0.10.0
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/dist/{TransportWebUSB-YQMAGJAJ.mjs → TransportWebUSB-2KITI5HD.mjs} +24 -12
- package/dist/browser.d.mts +4 -4
- package/dist/browser.d.ts +4 -4
- package/dist/browser.js +1358 -847
- package/dist/browser.mjs +13 -3
- package/dist/{chunk-64AYA5F5.mjs → chunk-G3TBBG2K.mjs} +221 -146
- package/dist/{chunk-4GRJ5MAW.mjs → chunk-KXETSSKP.mjs} +4 -0
- package/dist/{chunk-YWGJ77A2.mjs → chunk-PT2DNA7E.mjs} +335 -310
- package/dist/{constants-LHAAUC2T.mjs → constants-DCJYTIU3.mjs} +5 -1
- package/dist/{dist-2OGQ7FED.mjs → dist-PYEXZNFD.mjs} +609 -221
- package/dist/{index-DeE1ZzA4.d.mts → index-B1d8pihL.d.mts} +117 -33
- package/dist/{index-DXh2IGkz.d.ts → index-UQhQJZbM.d.ts} +117 -33
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1348 -837
- package/dist/index.mjs +13 -3
- package/dist/{interface-Bf7w1PLW.d.mts → interface-CQi0-WfS.d.mts} +2 -2
- package/dist/{interface-Bf7w1PLW.d.ts → interface-CQi0-WfS.d.ts} +2 -2
- package/dist/{noir-kzbLVTei.d.mts → noir-CwPIyBLj.d.mts} +1 -1
- package/dist/{noir-kzbLVTei.d.ts → noir-CwPIyBLj.d.ts} +1 -1
- package/dist/proofs/halo2.d.mts +1 -1
- package/dist/proofs/halo2.d.ts +1 -1
- package/dist/proofs/kimchi.d.mts +1 -1
- package/dist/proofs/kimchi.d.ts +1 -1
- package/dist/proofs/noir.d.mts +1 -1
- package/dist/proofs/noir.d.ts +1 -1
- package/dist/{solana-U3MEGU7W.mjs → solana-ZWNIQTSU.mjs} +6 -6
- package/package.json +32 -32
- package/src/adapters/gelato-relay.ts +386 -0
- package/src/adapters/index.ts +28 -0
- package/src/adapters/oneinch.ts +126 -0
- package/src/chains/ethereum/constants.ts +33 -1
- package/src/chains/ethereum/index.ts +2 -1
- package/src/chains/ethereum/privacy-adapter.ts +44 -26
- package/src/chains/ethereum/stealth.ts +84 -30
- package/src/chains/ethereum/types.ts +4 -0
- package/src/chains/near/privacy-adapter.ts +8 -5
- package/src/chains/near/resolver.ts +22 -8
- package/src/chains/near/stealth.ts +9 -9
- package/src/chains/solana/constants.ts +13 -1
- package/src/chains/solana/ephemeral-keys.ts +3 -257
- package/src/chains/solana/index.ts +2 -3
- package/src/chains/solana/providers/helius-enhanced.ts +6 -6
- package/src/chains/solana/providers/webhook.ts +2 -2
- package/src/chains/solana/scan.ts +9 -8
- package/src/chains/solana/stealth-scanner.ts +3 -3
- package/src/chains/solana/types.ts +18 -4
- package/src/cosmos/ibc-stealth.ts +6 -6
- package/src/index.ts +6 -0
- package/src/move/aptos.ts +15 -9
- package/src/move/sui.ts +15 -9
- package/src/nft/private-nft.ts +10 -6
- package/src/privacy-backends/shadowwire.ts +13 -0
- package/src/stealth/ed25519.ts +173 -12
- package/src/stealth/index.ts +47 -4
- package/src/stealth/secp256k1.ts +144 -7
- package/src/stealth.ts +7 -0
- package/src/wallet/ethereum/privacy-adapter.ts +1 -1
- package/src/wallet/hardware/ledger-privacy.ts +2 -2
- package/src/wallet/near/adapter.ts +2 -2
- package/src/wallet/near/meteor-wallet.ts +2 -2
- package/src/wallet/near/my-near-wallet.ts +2 -2
- package/src/wallet/near/wallet-selector.ts +2 -2
- package/src/wallet/solana/privacy-adapter.ts +9 -9
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gelato Relay Adapter for SIP Protocol
|
|
3
|
+
*
|
|
4
|
+
* Enables gasless claim and withdrawal from stealth addresses via Gelato Relay.
|
|
5
|
+
* Recipients can claim funds without holding ETH for gas — critical for stealth
|
|
6
|
+
* addresses which are freshly generated and have zero balance.
|
|
7
|
+
*
|
|
8
|
+
* ## Two Modes
|
|
9
|
+
*
|
|
10
|
+
* - **sponsoredCall**: SIP pays gas from Gas Tank (requires API key)
|
|
11
|
+
* - **callWithSyncFee**: Fee deducted from withdrawal amount (requires SIPRelayer)
|
|
12
|
+
*
|
|
13
|
+
* ```
|
|
14
|
+
* ┌──────────────────────────────────────────────────────────┐
|
|
15
|
+
* │ GELATO RELAY + SIP PRIVACY FLOW │
|
|
16
|
+
* │ │
|
|
17
|
+
* │ Sponsored Mode: │
|
|
18
|
+
* │ 1. Recipient calls sponsoredClaim() with proof │
|
|
19
|
+
* │ 2. Gelato relays tx to SIPPrivacy.withdrawDeposit() │
|
|
20
|
+
* │ 3. SIP Gas Tank pays the gas fee │
|
|
21
|
+
* │ 4. Funds arrive at recipient stealth address │
|
|
22
|
+
* │ │
|
|
23
|
+
* │ SyncFee Mode: │
|
|
24
|
+
* │ 1. Recipient calls syncFeeClaim() with proof + maxFee │
|
|
25
|
+
* │ 2. Gelato relays tx to SIPRelayer contract │
|
|
26
|
+
* │ 3. SIPRelayer calls SIPPrivacy.withdrawDeposit() │
|
|
27
|
+
* │ 4. SIPRelayer deducts gas fee from withdrawn amount │
|
|
28
|
+
* │ 5. Remainder arrives at recipient stealth address │
|
|
29
|
+
* │ │
|
|
30
|
+
* │ Result: Zero-gas claims from stealth addresses │
|
|
31
|
+
* └──────────────────────────────────────────────────────────┘
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* ## ABI Encoding
|
|
35
|
+
*
|
|
36
|
+
* Uses manual ABI encoding with @noble/hashes keccak256 for function selectors.
|
|
37
|
+
* No ethers.js or viem dependency required.
|
|
38
|
+
*
|
|
39
|
+
* @see https://docs.gelato.network/web3-services/relay
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import { keccak_256 } from '@noble/hashes/sha3'
|
|
43
|
+
import { bytesToHex } from '@noble/hashes/utils'
|
|
44
|
+
|
|
45
|
+
// ═══════════════════════════════════════════
|
|
46
|
+
// Types
|
|
47
|
+
// ═══════════════════════════════════════════
|
|
48
|
+
|
|
49
|
+
export interface GelatoRelayConfig {
|
|
50
|
+
/** Gelato Gas Tank API key (required for sponsoredCall) */
|
|
51
|
+
apiKey?: string
|
|
52
|
+
/** Target chain ID (e.g. 11155111 for Sepolia) */
|
|
53
|
+
chainId: number
|
|
54
|
+
/** SIPPrivacy contract address */
|
|
55
|
+
sipPrivacyAddress: string
|
|
56
|
+
/** SIPRelayer contract address (required for callWithSyncFee) */
|
|
57
|
+
sipRelayerAddress?: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface RelayClaimParams {
|
|
61
|
+
/** Deposit transfer ID */
|
|
62
|
+
transferId: bigint
|
|
63
|
+
/** Nullifier hash (bytes32 hex) */
|
|
64
|
+
nullifier: string
|
|
65
|
+
/** ZK proof (bytes hex) */
|
|
66
|
+
proof: string
|
|
67
|
+
/** Recipient address (20-byte hex) */
|
|
68
|
+
recipient: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface SyncFeeClaimParams extends RelayClaimParams {
|
|
72
|
+
/** Token to pay Gelato fee in */
|
|
73
|
+
feeToken: string
|
|
74
|
+
/** Maximum fee willing to pay */
|
|
75
|
+
maxFee: bigint
|
|
76
|
+
/** ERC20 token address (omit or zero address for ETH) */
|
|
77
|
+
token?: string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface RelayResult {
|
|
81
|
+
/** Gelato task ID for tracking */
|
|
82
|
+
taskId: string
|
|
83
|
+
/** Which relay mode was used */
|
|
84
|
+
mode: 'sponsored' | 'syncFee'
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type TaskStatus =
|
|
88
|
+
| 'CheckPending'
|
|
89
|
+
| 'ExecPending'
|
|
90
|
+
| 'ExecSuccess'
|
|
91
|
+
| 'ExecReverted'
|
|
92
|
+
| 'Cancelled'
|
|
93
|
+
|
|
94
|
+
export interface TaskStatusResult {
|
|
95
|
+
/** Gelato task ID */
|
|
96
|
+
taskId: string
|
|
97
|
+
/** Current task state */
|
|
98
|
+
taskState: TaskStatus
|
|
99
|
+
/** Transaction hash (available after execution) */
|
|
100
|
+
transactionHash?: string
|
|
101
|
+
/** Block number (available after execution) */
|
|
102
|
+
blockNumber?: number
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ═══════════════════════════════════════════
|
|
106
|
+
// Constants
|
|
107
|
+
// ═══════════════════════════════════════════
|
|
108
|
+
|
|
109
|
+
const GELATO_RELAY_URL = 'https://relay.gelato.digital'
|
|
110
|
+
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
|
111
|
+
|
|
112
|
+
// ═══════════════════════════════════════════
|
|
113
|
+
// ABI Encoding Helpers (no ethers/viem)
|
|
114
|
+
// ═══════════════════════════════════════════
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Compute 4-byte function selector from Solidity signature.
|
|
118
|
+
* selector = keccak256(signature)[0:4]
|
|
119
|
+
*/
|
|
120
|
+
export function functionSelector(signature: string): string {
|
|
121
|
+
const hash = keccak_256(new TextEncoder().encode(signature))
|
|
122
|
+
return '0x' + bytesToHex(hash).slice(0, 8)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Left-pad a bigint to 32 bytes (64 hex chars) */
|
|
126
|
+
function padUint256(value: bigint): string {
|
|
127
|
+
if (value < 0n) throw new Error(`uint256 cannot be negative: ${value}`)
|
|
128
|
+
if (value >= 2n ** 256n) throw new Error(`uint256 overflow: ${value}`)
|
|
129
|
+
return value.toString(16).padStart(64, '0')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Left-pad an address to 32 bytes (64 hex chars) */
|
|
133
|
+
function padAddress(addr: string): string {
|
|
134
|
+
const clean = addr.startsWith('0x') ? addr.slice(2) : addr
|
|
135
|
+
if (clean.length !== 40) {
|
|
136
|
+
throw new Error(`Invalid address length: expected 40 hex chars, got ${clean.length}`)
|
|
137
|
+
}
|
|
138
|
+
return clean.toLowerCase().padStart(64, '0')
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Right-pad a bytes32 value to 32 bytes (64 hex chars) */
|
|
142
|
+
function padBytes32(value: string): string {
|
|
143
|
+
const clean = value.startsWith('0x') ? value.slice(2) : value
|
|
144
|
+
if (clean.length > 64) {
|
|
145
|
+
throw new Error(`bytes32 value too long: ${clean.length} hex chars (max 64)`)
|
|
146
|
+
}
|
|
147
|
+
return clean.padEnd(64, '0')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* ABI-encode a dynamic `bytes` value.
|
|
152
|
+
* Returns: length (32 bytes) + data (padded to 32-byte boundary)
|
|
153
|
+
*/
|
|
154
|
+
function encodeBytes(value: string): string {
|
|
155
|
+
const clean = value.startsWith('0x') ? value.slice(2) : value
|
|
156
|
+
if (clean.length % 2 !== 0) {
|
|
157
|
+
throw new Error(`bytes value must have even hex length, got ${clean.length}`)
|
|
158
|
+
}
|
|
159
|
+
const length = clean.length / 2
|
|
160
|
+
const paddedData = clean.length % 64 === 0
|
|
161
|
+
? clean
|
|
162
|
+
: clean + '0'.repeat(64 - (clean.length % 64))
|
|
163
|
+
return padUint256(BigInt(length)) + paddedData
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ═══════════════════════════════════════════
|
|
167
|
+
// GelatoRelayAdapter
|
|
168
|
+
// ═══════════════════════════════════════════
|
|
169
|
+
|
|
170
|
+
export class GelatoRelayAdapter {
|
|
171
|
+
private config: GelatoRelayConfig
|
|
172
|
+
|
|
173
|
+
constructor(config: GelatoRelayConfig) {
|
|
174
|
+
if (!config.sipPrivacyAddress) {
|
|
175
|
+
throw new Error('sipPrivacyAddress is required')
|
|
176
|
+
}
|
|
177
|
+
if (!config.chainId) {
|
|
178
|
+
throw new Error('chainId is required')
|
|
179
|
+
}
|
|
180
|
+
this.config = config
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Gasless withdrawal via sponsoredCall (SIP pays gas from Gas Tank).
|
|
185
|
+
* Calls SIPPrivacy.withdrawDeposit() directly.
|
|
186
|
+
*/
|
|
187
|
+
async sponsoredClaim(params: RelayClaimParams): Promise<RelayResult> {
|
|
188
|
+
if (!this.config.apiKey) {
|
|
189
|
+
throw new Error('API key required for sponsoredCall')
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const data = this.encodeWithdrawDeposit(params)
|
|
193
|
+
|
|
194
|
+
const response = await fetch(`${GELATO_RELAY_URL}/relays/v2/sponsored-call`, {
|
|
195
|
+
method: 'POST',
|
|
196
|
+
headers: { 'Content-Type': 'application/json' },
|
|
197
|
+
body: JSON.stringify({
|
|
198
|
+
chainId: this.config.chainId,
|
|
199
|
+
target: this.config.sipPrivacyAddress,
|
|
200
|
+
data,
|
|
201
|
+
sponsorApiKey: this.config.apiKey,
|
|
202
|
+
}),
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
if (!response.ok) {
|
|
206
|
+
const text = await response.text().catch(() => response.statusText)
|
|
207
|
+
throw new Error(`Gelato relay error: ${response.status} ${text}`)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const result = await response.json()
|
|
211
|
+
return { taskId: result.taskId, mode: 'sponsored' }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Gasless withdrawal via callWithSyncFee (fee deducted from withdrawn amount).
|
|
216
|
+
* Routes through SIPRelayer contract which handles fee deduction.
|
|
217
|
+
*
|
|
218
|
+
* - ETH withdrawals: calls relayedWithdrawETH()
|
|
219
|
+
* - ERC20 withdrawals: calls relayedWithdrawToken() (when params.token is set)
|
|
220
|
+
*/
|
|
221
|
+
async syncFeeClaim(params: SyncFeeClaimParams): Promise<RelayResult> {
|
|
222
|
+
if (!this.config.sipRelayerAddress) {
|
|
223
|
+
throw new Error('SIPRelayer address required for callWithSyncFee')
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const isToken = params.token && params.token !== ZERO_ADDRESS
|
|
227
|
+
const data = isToken
|
|
228
|
+
? this.encodeRelayedWithdrawToken(params)
|
|
229
|
+
: this.encodeRelayedWithdrawETH(params)
|
|
230
|
+
|
|
231
|
+
const response = await fetch(`${GELATO_RELAY_URL}/relays/v2/call-with-sync-fee`, {
|
|
232
|
+
method: 'POST',
|
|
233
|
+
headers: { 'Content-Type': 'application/json' },
|
|
234
|
+
body: JSON.stringify({
|
|
235
|
+
chainId: this.config.chainId,
|
|
236
|
+
target: this.config.sipRelayerAddress,
|
|
237
|
+
data,
|
|
238
|
+
feeToken: params.feeToken,
|
|
239
|
+
isRelayContext: true,
|
|
240
|
+
}),
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
if (!response.ok) {
|
|
244
|
+
const text = await response.text().catch(() => response.statusText)
|
|
245
|
+
throw new Error(`Gelato relay error: ${response.status} ${text}`)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const result = await response.json()
|
|
249
|
+
return { taskId: result.taskId, mode: 'syncFee' }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Check relay task status.
|
|
254
|
+
* Poll this after submitting a relay request to track execution.
|
|
255
|
+
*/
|
|
256
|
+
async getTaskStatus(taskId: string): Promise<TaskStatusResult> {
|
|
257
|
+
if (!taskId) {
|
|
258
|
+
throw new Error('taskId is required')
|
|
259
|
+
}
|
|
260
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(taskId)) {
|
|
261
|
+
throw new Error(`Invalid taskId format: ${taskId}`)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const response = await fetch(`${GELATO_RELAY_URL}/tasks/status/${taskId}`)
|
|
265
|
+
|
|
266
|
+
if (!response.ok) {
|
|
267
|
+
throw new Error(`Gelato status error: ${response.status}`)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const result = await response.json()
|
|
271
|
+
return {
|
|
272
|
+
taskId: result.task.taskId,
|
|
273
|
+
taskState: result.task.taskState,
|
|
274
|
+
transactionHash: result.task.transactionHash,
|
|
275
|
+
blockNumber: result.task.blockNumber,
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ═══════════════════════════════════════════
|
|
280
|
+
// ABI Encoding (no ethers/viem dependency)
|
|
281
|
+
// ═══════════════════════════════════════════
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Encode withdrawDeposit(uint256,bytes32,bytes,address)
|
|
285
|
+
*
|
|
286
|
+
* ABI layout:
|
|
287
|
+
* [selector 4B]
|
|
288
|
+
* [0x00] transferId — uint256
|
|
289
|
+
* [0x20] nullifier — bytes32
|
|
290
|
+
* [0x40] proof offset — uint256 (points to 0x80)
|
|
291
|
+
* [0x60] recipient — address
|
|
292
|
+
* [0x80] proof length — uint256
|
|
293
|
+
* [0xa0] proof data — bytes (padded)
|
|
294
|
+
*/
|
|
295
|
+
private encodeWithdrawDeposit(params: RelayClaimParams): string {
|
|
296
|
+
const selector = functionSelector('withdrawDeposit(uint256,bytes32,bytes,address)')
|
|
297
|
+
const proofOffset = padUint256(128n) // 4 slots * 32 = 128
|
|
298
|
+
|
|
299
|
+
return selector
|
|
300
|
+
+ padUint256(params.transferId)
|
|
301
|
+
+ padBytes32(params.nullifier)
|
|
302
|
+
+ proofOffset
|
|
303
|
+
+ padAddress(params.recipient)
|
|
304
|
+
+ encodeBytes(params.proof)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Encode relayedWithdrawETH(uint256,bytes32,bytes,address,uint256)
|
|
309
|
+
*
|
|
310
|
+
* ABI layout:
|
|
311
|
+
* [selector 4B]
|
|
312
|
+
* [0x00] transferId — uint256
|
|
313
|
+
* [0x20] nullifier — bytes32
|
|
314
|
+
* [0x40] proof offset — uint256 (points to 0xa0)
|
|
315
|
+
* [0x60] recipient — address
|
|
316
|
+
* [0x80] maxFee — uint256
|
|
317
|
+
* [0xa0] proof length — uint256
|
|
318
|
+
* [0xc0] proof data — bytes (padded)
|
|
319
|
+
*/
|
|
320
|
+
private encodeRelayedWithdrawETH(params: SyncFeeClaimParams): string {
|
|
321
|
+
const selector = functionSelector('relayedWithdrawETH(uint256,bytes32,bytes,address,uint256)')
|
|
322
|
+
const proofOffset = padUint256(160n) // 5 slots * 32 = 160
|
|
323
|
+
|
|
324
|
+
return selector
|
|
325
|
+
+ padUint256(params.transferId)
|
|
326
|
+
+ padBytes32(params.nullifier)
|
|
327
|
+
+ proofOffset
|
|
328
|
+
+ padAddress(params.recipient)
|
|
329
|
+
+ padUint256(params.maxFee)
|
|
330
|
+
+ encodeBytes(params.proof)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Encode relayedWithdrawToken(uint256,bytes32,bytes,address,address,uint256)
|
|
335
|
+
*
|
|
336
|
+
* ABI layout:
|
|
337
|
+
* [selector 4B]
|
|
338
|
+
* [0x00] transferId — uint256
|
|
339
|
+
* [0x20] nullifier — bytes32
|
|
340
|
+
* [0x40] proof offset — uint256 (points to 0xc0)
|
|
341
|
+
* [0x60] recipient — address
|
|
342
|
+
* [0x80] token — address
|
|
343
|
+
* [0xa0] maxFee — uint256
|
|
344
|
+
* [0xc0] proof length — uint256
|
|
345
|
+
* [0xe0] proof data — bytes (padded)
|
|
346
|
+
*/
|
|
347
|
+
private encodeRelayedWithdrawToken(params: SyncFeeClaimParams): string {
|
|
348
|
+
const selector = functionSelector('relayedWithdrawToken(uint256,bytes32,bytes,address,address,uint256)')
|
|
349
|
+
const proofOffset = padUint256(192n) // 6 slots * 32 = 192
|
|
350
|
+
|
|
351
|
+
return selector
|
|
352
|
+
+ padUint256(params.transferId)
|
|
353
|
+
+ padBytes32(params.nullifier)
|
|
354
|
+
+ proofOffset
|
|
355
|
+
+ padAddress(params.recipient)
|
|
356
|
+
+ padAddress(params.token!)
|
|
357
|
+
+ padUint256(params.maxFee)
|
|
358
|
+
+ encodeBytes(params.proof)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Factory function for creating a GelatoRelayAdapter.
|
|
364
|
+
*
|
|
365
|
+
* @example
|
|
366
|
+
* ```typescript
|
|
367
|
+
* // Sponsored mode (SIP pays gas)
|
|
368
|
+
* const relay = createGelatoRelayAdapter({
|
|
369
|
+
* apiKey: 'gelato-api-key',
|
|
370
|
+
* chainId: 11155111,
|
|
371
|
+
* sipPrivacyAddress: '0x1FED...',
|
|
372
|
+
* })
|
|
373
|
+
* const result = await relay.sponsoredClaim({ transferId, nullifier, proof, recipient })
|
|
374
|
+
*
|
|
375
|
+
* // SyncFee mode (fee from withdrawal)
|
|
376
|
+
* const relay = createGelatoRelayAdapter({
|
|
377
|
+
* chainId: 11155111,
|
|
378
|
+
* sipPrivacyAddress: '0x1FED...',
|
|
379
|
+
* sipRelayerAddress: '0xABC...',
|
|
380
|
+
* })
|
|
381
|
+
* const result = await relay.syncFeeClaim({ transferId, nullifier, proof, recipient, feeToken, maxFee })
|
|
382
|
+
* ```
|
|
383
|
+
*/
|
|
384
|
+
export function createGelatoRelayAdapter(config: GelatoRelayConfig): GelatoRelayAdapter {
|
|
385
|
+
return new GelatoRelayAdapter(config)
|
|
386
|
+
}
|
package/src/adapters/index.ts
CHANGED
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
* ### Solana DEX
|
|
12
12
|
* - **JupiterAdapter** — Jupiter aggregator for private Solana swaps
|
|
13
13
|
*
|
|
14
|
+
* ### EVM Relay
|
|
15
|
+
* - **GelatoRelayAdapter** — Gasless claims from stealth addresses via Gelato Relay
|
|
16
|
+
*
|
|
14
17
|
* @example
|
|
15
18
|
* ```typescript
|
|
16
19
|
* import { JupiterAdapter, NEARIntentsAdapter } from '@sip-protocol/sdk'
|
|
@@ -58,3 +61,28 @@ export type {
|
|
|
58
61
|
JupiterSwapResult,
|
|
59
62
|
JupiterPrivateSwapResult,
|
|
60
63
|
} from './jupiter'
|
|
64
|
+
|
|
65
|
+
// 1inch Aggregator (EVM)
|
|
66
|
+
export { OneInchAdapter } from './oneinch'
|
|
67
|
+
|
|
68
|
+
export type {
|
|
69
|
+
OneInchQuote,
|
|
70
|
+
OneInchSwapData,
|
|
71
|
+
OneInchSwapParams,
|
|
72
|
+
} from './oneinch'
|
|
73
|
+
|
|
74
|
+
// Gelato Relay (EVM gasless)
|
|
75
|
+
export {
|
|
76
|
+
GelatoRelayAdapter,
|
|
77
|
+
createGelatoRelayAdapter,
|
|
78
|
+
functionSelector,
|
|
79
|
+
} from './gelato-relay'
|
|
80
|
+
|
|
81
|
+
export type {
|
|
82
|
+
GelatoRelayConfig,
|
|
83
|
+
RelayClaimParams,
|
|
84
|
+
SyncFeeClaimParams,
|
|
85
|
+
RelayResult,
|
|
86
|
+
TaskStatus,
|
|
87
|
+
TaskStatusResult,
|
|
88
|
+
} from './gelato-relay'
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 1inch Aggregator Adapter for SIP Protocol
|
|
3
|
+
*
|
|
4
|
+
* Generates swap calldata for privacy-preserving EVM swaps via 1inch aggregator.
|
|
5
|
+
* SIP adds stealth addresses as the swap recipient, breaking the on-chain identity link.
|
|
6
|
+
*
|
|
7
|
+
* ## Privacy Model
|
|
8
|
+
*
|
|
9
|
+
* 1inch swaps are routed through 200+ DEX pools for best price. SIP enhances privacy by:
|
|
10
|
+
* - Setting `destReceiver` to a stealth address (recipient unlinkable)
|
|
11
|
+
* - SIPSwapRouter validates calldata on-chain before forwarding to 1inch
|
|
12
|
+
*
|
|
13
|
+
* ```
|
|
14
|
+
* ┌──────────────────────────────────────────────────────────────┐
|
|
15
|
+
* │ 1INCH + SIP PRIVACY FLOW │
|
|
16
|
+
* │ │
|
|
17
|
+
* │ 1. SDK: Generate stealth address for output │
|
|
18
|
+
* │ 2. SDK: Call 1inch API (destReceiver = stealth) │
|
|
19
|
+
* │ 3. SDK: Submit calldata to SIPSwapRouter │
|
|
20
|
+
* │ 4. Contract: Validate calldata, deduct fee, forward │
|
|
21
|
+
* │ 5. 1inch Router → DEX pools → stealth address │
|
|
22
|
+
* │ │
|
|
23
|
+
* │ Result: Best-price swap, recipient unlinkable │
|
|
24
|
+
* └──────────────────────────────────────────────────────────────┘
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @see https://portal.1inch.dev
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export interface OneInchQuote {
|
|
31
|
+
toAmount: string
|
|
32
|
+
estimatedGas: string
|
|
33
|
+
protocols: Array<{ name: string; part: number }>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface OneInchSwapData {
|
|
37
|
+
tx: {
|
|
38
|
+
to: string
|
|
39
|
+
data: string
|
|
40
|
+
value: string
|
|
41
|
+
gas: number
|
|
42
|
+
}
|
|
43
|
+
toAmount: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface OneInchSwapParams {
|
|
47
|
+
src: string
|
|
48
|
+
dst: string
|
|
49
|
+
amount: string
|
|
50
|
+
from: string
|
|
51
|
+
destReceiver: string
|
|
52
|
+
slippage: number
|
|
53
|
+
disableEstimate?: boolean
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const CHAIN_IDS: Record<string, number> = {
|
|
57
|
+
ethereum: 1,
|
|
58
|
+
arbitrum: 42161,
|
|
59
|
+
optimism: 10,
|
|
60
|
+
base: 8453,
|
|
61
|
+
polygon: 137,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const ONEINCH_ROUTER = '0x111111125421cA6dc452d289314280a0f8842A65'
|
|
65
|
+
const API_BASE = 'https://api.1inch.dev/swap/v6.0'
|
|
66
|
+
|
|
67
|
+
export class OneInchAdapter {
|
|
68
|
+
private apiKey: string
|
|
69
|
+
private chainId: number
|
|
70
|
+
|
|
71
|
+
constructor(apiKey: string, chain: string | number) {
|
|
72
|
+
this.apiKey = apiKey
|
|
73
|
+
this.chainId = typeof chain === 'number' ? chain : CHAIN_IDS[chain]
|
|
74
|
+
if (!this.chainId) throw new Error(`Unsupported chain: ${chain}`)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
get routerAddress(): string {
|
|
78
|
+
return ONEINCH_ROUTER
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async getQuote(params: {
|
|
82
|
+
src: string
|
|
83
|
+
dst: string
|
|
84
|
+
amount: string
|
|
85
|
+
}): Promise<OneInchQuote> {
|
|
86
|
+
const url = new URL(`${API_BASE}/${this.chainId}/quote`)
|
|
87
|
+
url.searchParams.set('src', params.src)
|
|
88
|
+
url.searchParams.set('dst', params.dst)
|
|
89
|
+
url.searchParams.set('amount', params.amount)
|
|
90
|
+
|
|
91
|
+
const response = await fetch(url.toString(), {
|
|
92
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
throw new Error(`1inch API error: ${response.status} ${response.statusText}`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return response.json()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async getSwapCalldata(params: OneInchSwapParams): Promise<OneInchSwapData> {
|
|
103
|
+
const url = new URL(`${API_BASE}/${this.chainId}/swap`)
|
|
104
|
+
url.searchParams.set('src', params.src)
|
|
105
|
+
url.searchParams.set('dst', params.dst)
|
|
106
|
+
url.searchParams.set('amount', params.amount)
|
|
107
|
+
url.searchParams.set('from', params.from)
|
|
108
|
+
url.searchParams.set('destReceiver', params.destReceiver)
|
|
109
|
+
url.searchParams.set('slippage', params.slippage.toString())
|
|
110
|
+
url.searchParams.set('disableEstimate', (params.disableEstimate ?? true).toString())
|
|
111
|
+
|
|
112
|
+
const response = await fetch(url.toString(), {
|
|
113
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
throw new Error(`1inch API error: ${response.status} ${response.statusText}`)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return response.json()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
static supportedChains(): string[] {
|
|
124
|
+
return Object.keys(CHAIN_IDS)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -129,7 +129,7 @@ export const ETHEREUM_TOKEN_CONTRACTS = {
|
|
|
129
129
|
/** Tether USD */
|
|
130
130
|
USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
|
131
131
|
/** Dai Stablecoin */
|
|
132
|
-
DAI: '
|
|
132
|
+
DAI: '0x6B175474E89094C44Da98b954EeDeAC495271d0F',
|
|
133
133
|
/** Chainlink */
|
|
134
134
|
LINK: '0x514910771AF9Ca656af840dff83E8264EcF986CA',
|
|
135
135
|
/** Uniswap */
|
|
@@ -237,6 +237,38 @@ export const EIP5564_ANNOUNCER_ADDRESS = '0x55649E01B5Df198D18D95b5cc5051630cfD4
|
|
|
237
237
|
*/
|
|
238
238
|
export const EIP5564_REGISTRY_ADDRESS = '0x6538E6bf4B0eBd30A8Ea10e318b7AEb51A8E4b5c'
|
|
239
239
|
|
|
240
|
+
// ─── SIP Deployed Contract Addresses ──────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* SIP Privacy contract addresses per network
|
|
244
|
+
* Deployed via Foundry Deploy.s.sol
|
|
245
|
+
*/
|
|
246
|
+
export const SIP_CONTRACT_ADDRESSES: Partial<Record<EthereumNetwork, {
|
|
247
|
+
sipPrivacy: string
|
|
248
|
+
pedersenVerifier: string
|
|
249
|
+
zkVerifier: string
|
|
250
|
+
stealthAddressRegistry: string
|
|
251
|
+
}>> = {
|
|
252
|
+
sepolia: {
|
|
253
|
+
sipPrivacy: '0x0B0d06D6B5136d63Bd0817414E2D318999e50339',
|
|
254
|
+
pedersenVerifier: '0xEB14E9022A4c3DEED072DeC6b3858c19a00C87Db',
|
|
255
|
+
zkVerifier: '0x26988D988684627084e6ae113e0354f6bc56b126',
|
|
256
|
+
stealthAddressRegistry: '0x1f7f3edD264Cf255dD99Fd433eD9FADE427dEF99',
|
|
257
|
+
},
|
|
258
|
+
'optimism-sepolia': {
|
|
259
|
+
sipPrivacy: '0x0B0d06D6B5136d63Bd0817414E2D318999e50339',
|
|
260
|
+
pedersenVerifier: '0xEB14E9022A4c3DEED072DeC6b3858c19a00C87Db',
|
|
261
|
+
zkVerifier: '0x26988D988684627084e6ae113e0354f6bc56b126',
|
|
262
|
+
stealthAddressRegistry: '0x1f7f3edD264Cf255dD99Fd433eD9FADE427dEF99',
|
|
263
|
+
},
|
|
264
|
+
'base-sepolia': {
|
|
265
|
+
sipPrivacy: '0x0B0d06D6B5136d63Bd0817414E2D318999e50339',
|
|
266
|
+
pedersenVerifier: '0xEB14E9022A4c3DEED072DeC6b3858c19a00C87Db',
|
|
267
|
+
zkVerifier: '0x26988D988684627084e6ae113e0354f6bc56b126',
|
|
268
|
+
stealthAddressRegistry: '0x1f7f3edD264Cf255dD99Fd433eD9FADE427dEF99',
|
|
269
|
+
},
|
|
270
|
+
}
|
|
271
|
+
|
|
240
272
|
/**
|
|
241
273
|
* SIP announcement event signature (for log filtering)
|
|
242
274
|
* Announcement(uint256 indexed schemeId, address indexed stealthAddress, address indexed caller, bytes ephemeralPubKey, bytes metadata)
|
|
@@ -39,6 +39,7 @@ export {
|
|
|
39
39
|
isL2Network,
|
|
40
40
|
isValidEthAddress,
|
|
41
41
|
sanitizeUrl,
|
|
42
|
+
SIP_CONTRACT_ADDRESSES,
|
|
42
43
|
} from './constants'
|
|
43
44
|
|
|
44
45
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
@@ -83,7 +84,7 @@ export {
|
|
|
83
84
|
generateEthereumStealthAddress,
|
|
84
85
|
deriveEthereumStealthPrivateKey,
|
|
85
86
|
checkEthereumStealthAddress,
|
|
86
|
-
|
|
87
|
+
checkEthereumStealthByEthAddress,
|
|
87
88
|
stealthPublicKeyToEthAddress,
|
|
88
89
|
extractPublicKeys,
|
|
89
90
|
createMetaAddressFromPublicKeys,
|