@provex/extension-react 1.0.16
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/README.md +258 -0
- package/package.json +29 -0
- package/src/ZKTLSContext.tsx +28 -0
- package/src/global.d.ts +9 -0
- package/src/useZKTLS.ts +459 -0
- package/tsconfig.json +18 -0
package/README.md
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# @provex/extension-react
|
|
2
|
+
|
|
3
|
+
React hooks and context providers for integrating with the ZKTLS (Zero-Knowledge Transport Layer Security) browser extension.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This package provides React components and hooks that enable web applications to communicate with the ZKTLS browser extension for generating cryptographic proofs of payments. It handles the bridge between your React application and the browser extension, allowing users to prove they made payments through traditional payment providers without revealing sensitive account information.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @provex/extension-react
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
### 1. Wrap your app with the ZKTLS Provider
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { ZKTLSProvider } from '@provex/extension-react'
|
|
21
|
+
|
|
22
|
+
function App() {
|
|
23
|
+
return (
|
|
24
|
+
<ZKTLSProvider fallbackComponent={<div>Loading ZKTLS...</div>}>
|
|
25
|
+
<YourApp />
|
|
26
|
+
</ZKTLSProvider>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 2. Use the ZKTLS context in your components
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { useZKTLSContext } from '@provex/extension-react'
|
|
35
|
+
|
|
36
|
+
function PaymentProofComponent() {
|
|
37
|
+
const zktls = useZKTLSContext()
|
|
38
|
+
|
|
39
|
+
const handleGenerateProof = async () => {
|
|
40
|
+
if (!zktls.isInitialized) {
|
|
41
|
+
console.log('ZKTLS extension not ready')
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
// Request connection to extension
|
|
47
|
+
await zktls.requestConnection()
|
|
48
|
+
|
|
49
|
+
// Authenticate with payment provider
|
|
50
|
+
const metadata = await zktls.authenticate({
|
|
51
|
+
actionType: 'transfer_venmo',
|
|
52
|
+
platform: 'venmo'
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// Generate cryptographic proof
|
|
56
|
+
const proofs = await zktls.generateProofSeries({
|
|
57
|
+
platform: 'venmo',
|
|
58
|
+
provider: 'venmo',
|
|
59
|
+
intentHash: '0x1234...',
|
|
60
|
+
originalIndex: 0
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
console.log('Proofs generated:', proofs)
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error('Proof generation failed:', error)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<button onClick={handleGenerateProof}>
|
|
71
|
+
Generate Payment Proof
|
|
72
|
+
</button>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## API Reference
|
|
78
|
+
|
|
79
|
+
### `useZKTLS()` Hook
|
|
80
|
+
|
|
81
|
+
The main hook that provides all ZKTLS functionality. Returns an object with the following methods:
|
|
82
|
+
|
|
83
|
+
#### Connection Management
|
|
84
|
+
- `isInitialized: boolean` - Whether the ZKTLS extension is initialized
|
|
85
|
+
- `requestConnection(): Promise<boolean>` - Request connection to the extension
|
|
86
|
+
- `checkConnectionStatus(): Promise<Status>` - Check current connection status
|
|
87
|
+
- `getVersion(): Promise<string>` - Get extension version
|
|
88
|
+
|
|
89
|
+
#### Authentication & Proof Generation
|
|
90
|
+
- `authenticate(inputs: AuthenticationInputs): Promise<MetadataMessageResponse>` - Authenticate with a payment provider
|
|
91
|
+
- `generateProof(inputs: HexIntentGenerateProofInputs): Promise<GenerateProofResponse>` - Generate a single proof
|
|
92
|
+
- `generateProofSeries(inputs, mutations): Promise<NotaryRequest[]>` - Generate multiple proofs for complex verification
|
|
93
|
+
- `waitForProof(proofId: string): Promise<NotaryRequest>` - Wait for proof generation to complete
|
|
94
|
+
|
|
95
|
+
#### Proof Management
|
|
96
|
+
- `fetchProofById(proofId: string): Promise<NotaryRequest | null>` - Fetch a specific proof
|
|
97
|
+
- `fetchProofs(): Promise<FetchProofsResponse>` - Fetch all generated proofs
|
|
98
|
+
- `fetchGeneratedProofs(responses: GenerateProofResponse[]): Promise<NotaryRequest[]>` - Fetch multiple proofs by their responses
|
|
99
|
+
|
|
100
|
+
#### Extension Integration
|
|
101
|
+
- `openSidebar(route: string): Promise<void>` - Open the extension sidebar
|
|
102
|
+
- `onMetadataMessage(callback): () => void` - Subscribe to metadata messages
|
|
103
|
+
|
|
104
|
+
### `ZKTLSProvider` Component
|
|
105
|
+
|
|
106
|
+
React context provider that makes ZKTLS functionality available to child components.
|
|
107
|
+
|
|
108
|
+
**Props:**
|
|
109
|
+
- `children: ReactNode` - Child components
|
|
110
|
+
- `fallbackComponent?: ReactNode` - Component to show while ZKTLS is initializing
|
|
111
|
+
|
|
112
|
+
### `useZKTLSContext()` Hook
|
|
113
|
+
|
|
114
|
+
Hook to access the ZKTLS context within components. Must be used within a `ZKTLSProvider`.
|
|
115
|
+
|
|
116
|
+
**Returns:** The same object as `useZKTLS()`
|
|
117
|
+
|
|
118
|
+
**Throws:** Error if used outside of `ZKTLSProvider`
|
|
119
|
+
|
|
120
|
+
## Supported Payment Providers
|
|
121
|
+
|
|
122
|
+
The extension supports proof generation for the following payment providers:
|
|
123
|
+
|
|
124
|
+
- **Venmo** - Single proof generation
|
|
125
|
+
- **Zelle** - Multiple proof generation (supports Chase, Bank of America, Citi sub-providers)
|
|
126
|
+
- **CashApp** - Single proof generation
|
|
127
|
+
- **Revolut** - Single proof generation
|
|
128
|
+
- **Wise** - Single proof generation
|
|
129
|
+
- **MercadoPago** - Single proof generation
|
|
130
|
+
|
|
131
|
+
## Advanced Usage
|
|
132
|
+
|
|
133
|
+
### Progress Tracking for Proof Series
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
const generateProofWithProgress = async () => {
|
|
137
|
+
await zktls.generateProofSeries(
|
|
138
|
+
{
|
|
139
|
+
platform: 'chase',
|
|
140
|
+
provider: 'zelle',
|
|
141
|
+
intentHash: '0x1234...',
|
|
142
|
+
originalIndex: 0
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
progress: ({ progress, step, id, total }) => {
|
|
146
|
+
console.log(`Step ${step + 1}/${total}: ${progress}`)
|
|
147
|
+
if (id) console.log(`Proof ID: ${id}`)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Metadata Message Handling
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
const unsubscribe = zktls.onMetadataMessage((data) => {
|
|
159
|
+
console.log('Payment metadata:', data.metadata)
|
|
160
|
+
console.log('Platform:', data.platform)
|
|
161
|
+
console.log('Request ID:', data.requestId)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
return unsubscribe
|
|
165
|
+
}, [zktls])
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Finding Matching Payment Metadata
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import { findMetadataMessage } from '@provex/extension-react'
|
|
172
|
+
|
|
173
|
+
const matchingMessage = findMetadataMessage({
|
|
174
|
+
metadata: paymentMetadata,
|
|
175
|
+
intent: userIntent,
|
|
176
|
+
token: tokenInfo
|
|
177
|
+
})
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Proof Encoding Utilities
|
|
181
|
+
|
|
182
|
+
The package includes utilities for encoding proofs for blockchain submission:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import {
|
|
186
|
+
encodeProofAsBytes,
|
|
187
|
+
encodeTwoProofs,
|
|
188
|
+
encodeProof
|
|
189
|
+
} from '@provex/extension-react'
|
|
190
|
+
|
|
191
|
+
// Encode single proof
|
|
192
|
+
const encodedProof = encodeProofAsBytes(reclaimProof)
|
|
193
|
+
|
|
194
|
+
// Encode multiple proofs (for Zelle/Chase)
|
|
195
|
+
const encodedProofs = encodeTwoProofs(proof1, proof2)
|
|
196
|
+
|
|
197
|
+
// High-level encoding based on provider
|
|
198
|
+
const encoded = encodeProof({
|
|
199
|
+
extensionProofs: notarizedProofs,
|
|
200
|
+
subProvider: 'chase',
|
|
201
|
+
provider: 'zelle'
|
|
202
|
+
})
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Error Handling
|
|
206
|
+
|
|
207
|
+
Always wrap ZKTLS calls in try-catch blocks:
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
const handleProofGeneration = async () => {
|
|
211
|
+
try {
|
|
212
|
+
const proofs = await zktls.generateProofSeries(inputs)
|
|
213
|
+
// Handle success
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if (error.message.includes('failed')) {
|
|
216
|
+
// Handle proof generation failure
|
|
217
|
+
} else if (error.message.includes('cancelled')) {
|
|
218
|
+
// Handle user cancellation
|
|
219
|
+
} else {
|
|
220
|
+
// Handle other errors
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Types
|
|
227
|
+
|
|
228
|
+
The package exports TypeScript types for all major interfaces:
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
import type {
|
|
232
|
+
Status,
|
|
233
|
+
MetadataMessage,
|
|
234
|
+
MetadataMessageResponse,
|
|
235
|
+
GenerateProofInputs,
|
|
236
|
+
GenerateProofResponse,
|
|
237
|
+
AuthenticationInputs,
|
|
238
|
+
NotaryRequest,
|
|
239
|
+
ReclaimProof
|
|
240
|
+
} from '@provex/extension-react'
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Dependencies
|
|
244
|
+
|
|
245
|
+
- **React** ^18.2.0 (peer dependency)
|
|
246
|
+
- **@provex/utils** - Shared utilities package
|
|
247
|
+
- **viem** - Ethereum utilities for encoding
|
|
248
|
+
|
|
249
|
+
## Browser Extension Requirement
|
|
250
|
+
|
|
251
|
+
This package requires users to have the ZKTLS browser extension installed. The extension handles all cryptographic operations and secure communication with payment providers.
|
|
252
|
+
|
|
253
|
+
## Security Notes
|
|
254
|
+
|
|
255
|
+
- All sensitive operations are handled by the browser extension
|
|
256
|
+
- No private keys or sensitive data are exposed to the web application
|
|
257
|
+
- Zero-knowledge proofs ensure payment verification without revealing account details
|
|
258
|
+
- All proof generation happens locally in the user's browser
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@provex/extension-react",
|
|
3
|
+
"version": "1.0.16",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"access": "public",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts",
|
|
9
|
+
"./useZKTLS": "./src/useZKTLS.ts",
|
|
10
|
+
"./ZKTLSContext": "./src/ZKTLSContext.tsx"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@provex/utils": "^1.0.16",
|
|
17
|
+
"react": "^18.2.0",
|
|
18
|
+
"tsx": "^4.20.3",
|
|
19
|
+
"typescript": "^5.9.2",
|
|
20
|
+
"viem": "^2.38.0"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [],
|
|
23
|
+
"author": "",
|
|
24
|
+
"license": "ISC",
|
|
25
|
+
"description": "",
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/react": "^19.2.2"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createContext, useContext, type ReactNode } from 'react'
|
|
2
|
+
import { useZKTLS } from './useZKTLS'
|
|
3
|
+
|
|
4
|
+
type ZKTLSContextType = ReturnType<typeof useZKTLS>
|
|
5
|
+
|
|
6
|
+
const ZKTLSContext = createContext<ZKTLSContextType | undefined>(undefined)
|
|
7
|
+
|
|
8
|
+
interface ZKTLSProviderProps {
|
|
9
|
+
children: ReactNode
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function ZKTLSProvider({ children }: ZKTLSProviderProps) {
|
|
13
|
+
const zktlsHook = useZKTLS()
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<ZKTLSContext.Provider value={zktlsHook}>
|
|
17
|
+
{children}
|
|
18
|
+
</ZKTLSContext.Provider>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function useZKTLSContext() {
|
|
23
|
+
const context = useContext(ZKTLSContext)
|
|
24
|
+
if (context === undefined) {
|
|
25
|
+
throw new Error('useZKTLSContext must be used within a ZKTLSProvider')
|
|
26
|
+
}
|
|
27
|
+
return context
|
|
28
|
+
}
|
package/src/global.d.ts
ADDED
package/src/useZKTLS.ts
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from "react"
|
|
2
|
+
import { encodeAbiParameters, encodePacked, parseAbiParameter, parseUnits, type Hex } from "viem"
|
|
3
|
+
import { interval, sleep } from "@provex/utils/time"
|
|
4
|
+
import type { IntentStruct } from "@provex/utils/intent"
|
|
5
|
+
import type { TokenInfo } from "@provex/utils/tokens"
|
|
6
|
+
import { providers, subProviderConfigs, subProviders, type ActionType, type PlatformKey, type ProviderKey, type SubProviderKey } from "@provex/utils/payment"
|
|
7
|
+
import { convertTokenInputToCurrency } from "@provex/utils/conversionRates"
|
|
8
|
+
|
|
9
|
+
declare global {
|
|
10
|
+
interface ZKTLS {
|
|
11
|
+
requestConnection(): Promise<boolean>
|
|
12
|
+
checkConnectionStatus(): Promise<Status>
|
|
13
|
+
getVersion(): Promise<string>
|
|
14
|
+
authenticate(inputs: AuthenticationInputs): Promise<void>
|
|
15
|
+
generateProof(inputs: GenerateProofInputs): Promise<GenerateProofResponse>
|
|
16
|
+
fetchProofById(proofId: string): Promise<ProofResponse>
|
|
17
|
+
fetchProofs(): Promise<FetchProofsResponse>
|
|
18
|
+
openSidebar(route: string): Promise<void>
|
|
19
|
+
onMetadataMessage(fn: (data: MetadataMessageResponse) => void): () => void
|
|
20
|
+
}
|
|
21
|
+
interface Window {
|
|
22
|
+
zktls: ZKTLS | undefined
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** the status of the zktls extension */
|
|
27
|
+
export type Status = 'connected' | 'disconnected' | 'pending'
|
|
28
|
+
|
|
29
|
+
/** the individual metadata message sent by the zktls extension */
|
|
30
|
+
export interface MetadataMessage {
|
|
31
|
+
amount: number
|
|
32
|
+
date: string
|
|
33
|
+
hidden: boolean
|
|
34
|
+
originalIndex: number
|
|
35
|
+
paymentId: number
|
|
36
|
+
recipient: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** the response from the zktls extension when a metadata message is received */
|
|
40
|
+
export interface MetadataMessageResponse {
|
|
41
|
+
expiresAt: number
|
|
42
|
+
platform: ProviderKey
|
|
43
|
+
requestId: string
|
|
44
|
+
metadata: MetadataMessage[]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** the response from the zktls extension when fetching proofs */
|
|
48
|
+
export interface FetchProofsResponse {
|
|
49
|
+
notaryRequests: NotaryRequest[]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** the inputs for the generateProof function */
|
|
53
|
+
export type GenerateProofInputsAbstracted = {
|
|
54
|
+
platform: PlatformKey
|
|
55
|
+
provider: ProviderKey
|
|
56
|
+
intentHash: Hex
|
|
57
|
+
originalIndex: number
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** the inputs for the generateProof function */
|
|
61
|
+
export type GenerateProofInputs = {
|
|
62
|
+
platform: ProviderKey
|
|
63
|
+
originalIndex: number
|
|
64
|
+
proofIndex?: number
|
|
65
|
+
intentHash: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* inputs for the generate proof function that requires the intent hash
|
|
70
|
+
* as hex to ensure it can be converted to a bigint string accurately
|
|
71
|
+
*/
|
|
72
|
+
export type HexIntentGenerateProofInputs = Exclude<GenerateProofInputs, 'intentHash'> & { intentHash: Hex }
|
|
73
|
+
|
|
74
|
+
/** the response from the zktls extension when generating a proof */
|
|
75
|
+
export type GenerateProofResponse = {
|
|
76
|
+
proofId: string
|
|
77
|
+
platform: ProviderKey
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** the inputs for the authenticate function */
|
|
81
|
+
export type AuthenticationInputs = {
|
|
82
|
+
actionType: ActionType
|
|
83
|
+
platform: PlatformKey
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** the response from the zktls extension when fetching a proof */
|
|
87
|
+
export interface Proof {
|
|
88
|
+
claimInfo: ClaimInfo
|
|
89
|
+
signedClaim: SignedClaim
|
|
90
|
+
isAppclipProof: boolean
|
|
91
|
+
status: string
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** the response from the zktls extension when fetching a proof */
|
|
95
|
+
export interface NotarizedProof {
|
|
96
|
+
claim: {
|
|
97
|
+
context: string
|
|
98
|
+
epoch: number
|
|
99
|
+
identifier: Hex
|
|
100
|
+
owner: Hex
|
|
101
|
+
parameters: string
|
|
102
|
+
provider: string
|
|
103
|
+
timestampS: number
|
|
104
|
+
},
|
|
105
|
+
signatures: {
|
|
106
|
+
attestorAddress: Hex
|
|
107
|
+
claimSignature: Record<number, number>
|
|
108
|
+
resultSignature: Record<number, number>
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** the claim info for a proof */
|
|
113
|
+
export interface ClaimInfo {
|
|
114
|
+
provider: string
|
|
115
|
+
parameters: string
|
|
116
|
+
context: string
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** the complete claim data for a proof */
|
|
120
|
+
export interface CompleteClaimData {
|
|
121
|
+
identifier: Hex
|
|
122
|
+
owner: Hex
|
|
123
|
+
timestampS: number
|
|
124
|
+
epoch: number
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** the signed claim for a proof */
|
|
128
|
+
export interface SignedClaim {
|
|
129
|
+
claim: CompleteClaimData
|
|
130
|
+
signatures: Hex[]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** the reclaim proof for a proof */
|
|
134
|
+
export type ReclaimProof = {
|
|
135
|
+
claimInfo: ClaimInfo
|
|
136
|
+
signedClaim: SignedClaim
|
|
137
|
+
isAppclipProof: boolean
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** the notary request for a proof */
|
|
141
|
+
export interface NotaryRequest {
|
|
142
|
+
metadata: (string | number)[]
|
|
143
|
+
actionType: ActionType
|
|
144
|
+
id: string
|
|
145
|
+
status: string
|
|
146
|
+
timestamp: number
|
|
147
|
+
proof: NotarizedProof
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** the response from the zktls extension when fetching a proof */
|
|
151
|
+
export interface ProofResponse {
|
|
152
|
+
notaryRequest: NotaryRequest
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export const extensionUrl = 'https://chromewebstore.google.com/detail/ijpgccednehjpeclfcllnjjcmiohdjih'
|
|
156
|
+
|
|
157
|
+
/** convert a byte array to a hex string */
|
|
158
|
+
export const byteArrayToHexString = (byteArray: number[]): Hex => {
|
|
159
|
+
return `0x${byteArray
|
|
160
|
+
.map(byte => byte.toString(16).padStart(2, '0'))
|
|
161
|
+
.join('')}`
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** parse a proof object from the zktls extension */
|
|
165
|
+
export const parseExtensionProof = (proofObject: NotarizedProof) => {
|
|
166
|
+
return {
|
|
167
|
+
claimInfo: {
|
|
168
|
+
provider: proofObject.claim.provider,
|
|
169
|
+
parameters: proofObject.claim.parameters,
|
|
170
|
+
context: proofObject.claim.context
|
|
171
|
+
},
|
|
172
|
+
signedClaim: {
|
|
173
|
+
claim: {
|
|
174
|
+
identifier: proofObject.claim.identifier as Hex,
|
|
175
|
+
owner: proofObject.claim.owner as Hex,
|
|
176
|
+
timestampS: proofObject.claim.timestampS,
|
|
177
|
+
epoch: proofObject.claim.epoch
|
|
178
|
+
},
|
|
179
|
+
signatures: [byteArrayToHexString(Object.values(proofObject.signatures.claimSignature))]
|
|
180
|
+
},
|
|
181
|
+
isAppclipProof: false
|
|
182
|
+
} as ReclaimProof
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** the abi encoding for a proof */
|
|
186
|
+
export const PROOF_ENCODING = parseAbiParameter("((string provider, string parameters, string context) claimInfo, ((bytes32 identifier, address owner, uint32 timestampS, uint32 epoch) claim, bytes[] signatures) signedClaim, bool isAppclipProof)")
|
|
187
|
+
|
|
188
|
+
/** encode a single proof as a bytes array */
|
|
189
|
+
export function encodeProofAsBytes(proof: ReclaimProof): Hex {
|
|
190
|
+
return encodeAbiParameters([PROOF_ENCODING], [proof])
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** encode two proofs to be passed to the underlying sub-provider */
|
|
194
|
+
export const encodeTwoProofs = (proof1: ReclaimProof, proof2: ReclaimProof) => {
|
|
195
|
+
return encodeAbiParameters([PROOF_ENCODING, PROOF_ENCODING], [proof1, proof2])
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** a high level function to encode a proof for a given provider and sub-provider */
|
|
199
|
+
export const encodeProof = ({ extensionProofs, subProvider, provider }: {
|
|
200
|
+
extensionProofs: NotarizedProof[],
|
|
201
|
+
subProvider: PlatformKey | null,
|
|
202
|
+
provider: ProviderKey
|
|
203
|
+
}) => {
|
|
204
|
+
const reclaimProofs = extensionProofs.map((p) => parseExtensionProof(p))
|
|
205
|
+
if (provider === 'zelle') {
|
|
206
|
+
// move this to check the blockchain for the payment method
|
|
207
|
+
const paymentMethod = subProviderConfigs![provider]![subProvider as SubProviderKey]!.paymentMethod
|
|
208
|
+
const twoEncoded = reclaimProofs.length === 2
|
|
209
|
+
? encodeTwoProofs(reclaimProofs[0], reclaimProofs[1])
|
|
210
|
+
: encodeProofAsBytes(reclaimProofs[0])
|
|
211
|
+
// select between various possible sub-providers
|
|
212
|
+
const withPaymentMethod = encodeProofWithPaymentMethod({
|
|
213
|
+
paymentMethod,
|
|
214
|
+
proof: twoEncoded,
|
|
215
|
+
})
|
|
216
|
+
return withPaymentMethod
|
|
217
|
+
}
|
|
218
|
+
const [reclaimProof] = reclaimProofs
|
|
219
|
+
return encodeProofAsBytes(reclaimProof)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* wrap a proof with a payment method and proof
|
|
224
|
+
* this allows us to use a single verifier as an entry point for multiple underlying sub-providers
|
|
225
|
+
*/
|
|
226
|
+
export const encodeProofWithPaymentMethod = ({
|
|
227
|
+
proof,
|
|
228
|
+
paymentMethod
|
|
229
|
+
}: {
|
|
230
|
+
proof: Hex,
|
|
231
|
+
paymentMethod: number
|
|
232
|
+
}): Hex => {
|
|
233
|
+
return encodePacked(['uint8', 'bytes'], [paymentMethod, proof])
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** the progress of a proof series */
|
|
237
|
+
export enum SeriesProgress {
|
|
238
|
+
Generating,
|
|
239
|
+
Polling,
|
|
240
|
+
Complete,
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export type ProgressInfo = {
|
|
244
|
+
progress: SeriesProgress
|
|
245
|
+
total: number
|
|
246
|
+
step: number | null
|
|
247
|
+
id: string | null
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** the mutations for the generate proof series function */
|
|
251
|
+
export type GenerateProofSeriesMutations = {
|
|
252
|
+
progress?: ({ progress, step, id }: ProgressInfo) => void
|
|
253
|
+
setOwnerName?: (name: string) => void
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** the zktls context */
|
|
257
|
+
export const useZKTLS = () => {
|
|
258
|
+
const [isInitialized, setIsInitialized] = useState(false)
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
const setup = () => {
|
|
261
|
+
setIsInitialized(true)
|
|
262
|
+
}
|
|
263
|
+
const eventName = 'zktls#initialized'
|
|
264
|
+
if (typeof window.zktls !== 'undefined') {
|
|
265
|
+
// api is available
|
|
266
|
+
setup()
|
|
267
|
+
} else {
|
|
268
|
+
window.addEventListener(eventName, setup)
|
|
269
|
+
return () => {
|
|
270
|
+
window.removeEventListener(eventName, setup)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}, [])
|
|
274
|
+
return useMemo(() => {
|
|
275
|
+
/** fetch a proof by its id */
|
|
276
|
+
const fetchProofById = async (proofId: string) => {
|
|
277
|
+
const proof = await window.zktls!.fetchProofById(proofId)
|
|
278
|
+
if (proof && proof.notaryRequest) return proof.notaryRequest
|
|
279
|
+
const proofs = await window.zktls!.fetchProofs()
|
|
280
|
+
const proofFromList = proofs.notaryRequests.find((p) => p.id === proofId)
|
|
281
|
+
if (proofFromList) return proofFromList
|
|
282
|
+
return null
|
|
283
|
+
}
|
|
284
|
+
/** wait for a proof to be generated */
|
|
285
|
+
const waitForProof = async (proofId: string, onCurrent?: (notaryRequest: NotaryRequest | null) => void) => {
|
|
286
|
+
let notaryRequest: NotaryRequest | null = null
|
|
287
|
+
do {
|
|
288
|
+
notaryRequest = await fetchProofById(proofId)
|
|
289
|
+
onCurrent?.(notaryRequest)
|
|
290
|
+
await sleep(1_000)
|
|
291
|
+
} while (!notaryRequest || !notaryRequest.status || notaryRequest.status === 'pending')
|
|
292
|
+
if (notaryRequest.status === 'failed' || notaryRequest.status === 'expired' || notaryRequest.status === 'cancelled' || notaryRequest.status === 'error') {
|
|
293
|
+
console.log(notaryRequest)
|
|
294
|
+
throw new Error(`Proof generation failed: ${notaryRequest.status}`)
|
|
295
|
+
}
|
|
296
|
+
return notaryRequest
|
|
297
|
+
}
|
|
298
|
+
/** generate a proof */
|
|
299
|
+
const generateProof = async (inputs: HexIntentGenerateProofInputs): Promise<GenerateProofResponse> => {
|
|
300
|
+
return await window.zktls!.generateProof({
|
|
301
|
+
proofIndex: 0,
|
|
302
|
+
...inputs,
|
|
303
|
+
intentHash: BigInt(inputs.intentHash).toString(),
|
|
304
|
+
})
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
/** check if the zktls extension is initialized */
|
|
308
|
+
isInitialized,
|
|
309
|
+
/** request a connection to the zktls extension */
|
|
310
|
+
requestConnection: async () => {
|
|
311
|
+
if (!window.zktls) return false
|
|
312
|
+
return window.zktls!.requestConnection()
|
|
313
|
+
},
|
|
314
|
+
/**
|
|
315
|
+
* check the connection status of the zktls extension
|
|
316
|
+
* @returns the connection status
|
|
317
|
+
*/
|
|
318
|
+
checkConnectionStatus: async () => {
|
|
319
|
+
return await window.zktls!.checkConnectionStatus()
|
|
320
|
+
},
|
|
321
|
+
/** get the version of the zktls extension */
|
|
322
|
+
getVersion: async () => {
|
|
323
|
+
return await window.zktls?.getVersion()
|
|
324
|
+
},
|
|
325
|
+
/** authenticate with the zktls extension */
|
|
326
|
+
authenticate: async (inputs: AuthenticationInputs) => {
|
|
327
|
+
let resolve: (value: MetadataMessageResponse) => void = () => { }
|
|
328
|
+
const unsub = window.zktls!.onMetadataMessage((data) => {
|
|
329
|
+
console.log('metadata message', data, inputs)
|
|
330
|
+
if (`transfer_${data.platform}` === inputs.actionType) {
|
|
331
|
+
resolve(data)
|
|
332
|
+
}
|
|
333
|
+
})
|
|
334
|
+
const promise = new Promise<MetadataMessageResponse>((resolver) => {
|
|
335
|
+
resolve = resolver
|
|
336
|
+
})
|
|
337
|
+
await window.zktls!.authenticate(inputs)
|
|
338
|
+
return await promise.then((data) => {
|
|
339
|
+
unsub()
|
|
340
|
+
return data
|
|
341
|
+
})
|
|
342
|
+
},
|
|
343
|
+
/** generate a proof */
|
|
344
|
+
generateProof,
|
|
345
|
+
/** wait for a proof to be generated */
|
|
346
|
+
waitForProof,
|
|
347
|
+
/** generate a proof series */
|
|
348
|
+
generateProofSeries: async (
|
|
349
|
+
inputs: GenerateProofInputsAbstracted,
|
|
350
|
+
{ progress: progressCallback }: GenerateProofSeriesMutations = {},
|
|
351
|
+
): Promise<NotaryRequest[]> => {
|
|
352
|
+
const { platform, provider, intentHash, originalIndex } = inputs
|
|
353
|
+
const args = {
|
|
354
|
+
platform: provider,
|
|
355
|
+
originalIndex,
|
|
356
|
+
intentHash,
|
|
357
|
+
} as const
|
|
358
|
+
const expectedTotal = provider === providers.ZELLE && platform === subProviders.CHASE ? 2 : 1
|
|
359
|
+
progressCallback?.({ progress: SeriesProgress.Generating, step: 0, id: null, total: expectedTotal })
|
|
360
|
+
const genProofA = await generateProof({ ...args, proofIndex: 0 })
|
|
361
|
+
progressCallback?.({ progress: SeriesProgress.Polling, step: 0, id: genProofA.proofId, total: expectedTotal })
|
|
362
|
+
const proofA = await waitForProof(genProofA.proofId)
|
|
363
|
+
if (platform !== subProviders.CHASE || provider !== providers.ZELLE) {
|
|
364
|
+
progressCallback?.({ progress: SeriesProgress.Complete, step: null, id: null, total: expectedTotal })
|
|
365
|
+
return [proofA]
|
|
366
|
+
}
|
|
367
|
+
progressCallback?.({ progress: SeriesProgress.Generating, step: 1, id: null, total: expectedTotal })
|
|
368
|
+
const genProofB = await generateProof({ ...args, proofIndex: 1 })
|
|
369
|
+
progressCallback?.({ progress: SeriesProgress.Polling, step: 1, id: genProofB.proofId, total: expectedTotal })
|
|
370
|
+
const proofB = await waitForProof(genProofB.proofId)
|
|
371
|
+
progressCallback?.({ progress: SeriesProgress.Complete, step: null, id: null, total: expectedTotal })
|
|
372
|
+
return [proofA, proofB]
|
|
373
|
+
},
|
|
374
|
+
/** subscribe to metadata messages */
|
|
375
|
+
onMetadataMessage: (fn: (data: any) => void) => {
|
|
376
|
+
return window.zktls!.onMetadataMessage(fn)
|
|
377
|
+
},
|
|
378
|
+
/** fetch a proof by its id */
|
|
379
|
+
fetchProofById,
|
|
380
|
+
/** fetch generated proofs */
|
|
381
|
+
fetchGeneratedProofs: async (genProofResponses: GenerateProofResponse[]) => {
|
|
382
|
+
return await Promise.all(genProofResponses.map((r) => fetchProofById(r.proofId)))
|
|
383
|
+
},
|
|
384
|
+
/** fetch all proofs */
|
|
385
|
+
fetchProofs: async () => {
|
|
386
|
+
return await window.zktls!.fetchProofs()
|
|
387
|
+
},
|
|
388
|
+
/** open the zktls sidebar */
|
|
389
|
+
openSidebar: async (route: string) => {
|
|
390
|
+
return await window.zktls!.openSidebar(route)
|
|
391
|
+
},
|
|
392
|
+
}
|
|
393
|
+
}, [isInitialized])
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** find the metadata message that matches the intent to the best of our ability */
|
|
397
|
+
export const findMetadataMessage = ({ metadata, intent, token, recipient, centIsInt = false }: {
|
|
398
|
+
metadata: MetadataMessage[],
|
|
399
|
+
intent: IntentStruct,
|
|
400
|
+
token: TokenInfo,
|
|
401
|
+
recipient: string,
|
|
402
|
+
centIsInt?: boolean,
|
|
403
|
+
}): MetadataMessage | null => {
|
|
404
|
+
const currencyAmount = convertTokenInputToCurrency({
|
|
405
|
+
token,
|
|
406
|
+
amountOutInt: intent.amount,
|
|
407
|
+
rate: intent.conversionRate,
|
|
408
|
+
})
|
|
409
|
+
const convertDecimalToBigInt = (amount: number) => parseUnits(amount.toString(), token.decimals)
|
|
410
|
+
const amountFilter = (m: MetadataMessage) => {
|
|
411
|
+
let converted = convertDecimalToBigInt(m.amount)
|
|
412
|
+
if (centIsInt) {
|
|
413
|
+
converted = converted / 100n
|
|
414
|
+
}
|
|
415
|
+
return converted === currencyAmount || converted === (currencyAmount * -1n)
|
|
416
|
+
}
|
|
417
|
+
const timeFilter = (m: MetadataMessage, onlyRequireLessThan?: boolean) => {
|
|
418
|
+
const date = new Date(m.date)
|
|
419
|
+
let year, month, day = 0
|
|
420
|
+
if (m.date.length) {
|
|
421
|
+
year = +m.date.slice(0, 4)
|
|
422
|
+
month = +m.date.slice(4, 6)
|
|
423
|
+
day = +m.date.slice(6, 8)
|
|
424
|
+
} else {
|
|
425
|
+
year = date.getFullYear()
|
|
426
|
+
month = date.getMonth() + 1
|
|
427
|
+
day = date.getDate()
|
|
428
|
+
}
|
|
429
|
+
const timestamp = new Date(`${year}-${month}-${day}`)
|
|
430
|
+
const tsmp = Number(intent.timestamp) * 1_000
|
|
431
|
+
// intent happens first
|
|
432
|
+
const intentTimestamp0 = new Date(tsmp - (tsmp % interval.day))
|
|
433
|
+
const intentTimestamp1 = new Date(tsmp - (tsmp % interval.day) + interval.day)
|
|
434
|
+
// if the transaction timestamp is within 1 day of the intent timestamp, it's a match
|
|
435
|
+
const txTsmp = timestamp.getTime()
|
|
436
|
+
if (onlyRequireLessThan) {
|
|
437
|
+
return intentTimestamp0.getTime() <= txTsmp
|
|
438
|
+
}
|
|
439
|
+
return intentTimestamp0.getTime() === txTsmp || intentTimestamp1.getTime() === txTsmp
|
|
440
|
+
}
|
|
441
|
+
const filtered = metadata.filter((m) => !m.hidden && m.recipient === recipient)
|
|
442
|
+
const foundWithinDay = filtered.find((m) => amountFilter(m) && timeFilter(m))
|
|
443
|
+
if (foundWithinDay) {
|
|
444
|
+
console.log('found within day', foundWithinDay, intent)
|
|
445
|
+
return foundWithinDay
|
|
446
|
+
}
|
|
447
|
+
const foundWithBound = metadata.find((m) => amountFilter(m) && timeFilter(m, true))
|
|
448
|
+
if (foundWithBound) {
|
|
449
|
+
console.log('found with bound', foundWithBound, intent)
|
|
450
|
+
return foundWithBound
|
|
451
|
+
}
|
|
452
|
+
const foundByAmountOnly = metadata.find(amountFilter)
|
|
453
|
+
if (foundByAmountOnly) {
|
|
454
|
+
console.log('found by amount only', foundByAmountOnly, intent)
|
|
455
|
+
return foundByAmountOnly
|
|
456
|
+
}
|
|
457
|
+
console.log('no match', intent, metadata, convertDecimalToBigInt(metadata[0].amount), currencyAmount)
|
|
458
|
+
return null
|
|
459
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"module": "NodeNext",
|
|
6
|
+
"composite": true,
|
|
7
|
+
"allowJs": true,
|
|
8
|
+
"outDir": "./dist",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"moduleDetection": "force",
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"moduleResolution": "nodenext"
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.json", "src/**/*.svg"]
|
|
18
|
+
}
|