privy-starknet-provider 1.0.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/README.md +250 -0
- package/dist/StarknetProvider.d.ts +10 -0
- package/dist/StarknetProvider.d.ts.map +1 -0
- package/dist/StarknetProvider.js +273 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/package.json +54 -0
- package/dist/types.d.ts +84 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/useStarknet.d.ts +34 -0
- package/dist/useStarknet.d.ts.map +1 -0
- package/dist/useStarknet.js +43 -0
- package/dist/utils.d.ts +53 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +157 -0
- package/package.json +54 -0
- package/src/StarknetProvider.tsx +298 -0
- package/src/index.ts +27 -0
- package/src/types.ts +90 -0
- package/src/useStarknet.ts +44 -0
- package/src/utils.ts +145 -0
package/README.md
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# privy-starknet-provider
|
|
2
|
+
|
|
3
|
+
Starknet wallet provider for React Native apps using Privy authentication and AVNU paymaster for gasless transactions.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Privy Authentication - Seamless embedded wallet creation with email/social login
|
|
8
|
+
- Deterministic Key Derivation - Same user always gets the same Starknet address
|
|
9
|
+
- Gasless Transactions - AVNU paymaster integration for sponsored transactions
|
|
10
|
+
- Multi-Token Support - ETH and STRK balance tracking
|
|
11
|
+
- React Native Ready - Built specifically for Expo/React Native with proper polyfills
|
|
12
|
+
-
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @keep-starknet-strange/privy-starknet-provider
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Peer Dependencies
|
|
20
|
+
|
|
21
|
+
Install these required peer dependencies:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install react react-native expo @privy-io/expo expo-crypto
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Required Polyfills
|
|
28
|
+
|
|
29
|
+
Add these imports to your app's entry point (e.g., `index.js`):
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
import 'fast-text-encoding';
|
|
33
|
+
import 'react-native-get-random-values';
|
|
34
|
+
import '@ethersproject/shims';
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Install polyfill dependencies:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install fast-text-encoding react-native-get-random-values @ethersproject/shims readable-stream
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Metro Configuration
|
|
44
|
+
|
|
45
|
+
Create or update `metro.config.js` in your project root:
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
const { getDefaultConfig } = require('expo/metro-config');
|
|
49
|
+
const path = require('path');
|
|
50
|
+
|
|
51
|
+
const config = getDefaultConfig(__dirname);
|
|
52
|
+
|
|
53
|
+
config.resolver.unstable_conditionNames = ['browser', 'require', 'react-native'];
|
|
54
|
+
|
|
55
|
+
const projectRoot = __dirname;
|
|
56
|
+
const libraryRoot = path.resolve(projectRoot, '..'); // Adjust if needed
|
|
57
|
+
|
|
58
|
+
// Watch library directory if using local development
|
|
59
|
+
config.watchFolders = [libraryRoot];
|
|
60
|
+
|
|
61
|
+
// Only use your app's node_modules to avoid duplicate React
|
|
62
|
+
config.resolver.nodeModulesPaths = [
|
|
63
|
+
path.resolve(projectRoot, 'node_modules'),
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
// Block parent node_modules if in monorepo
|
|
67
|
+
config.resolver.blockList = [
|
|
68
|
+
new RegExp(`${libraryRoot.replace(/\//g, '\\/')}/node_modules/.*`),
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
// Configure polyfills
|
|
72
|
+
config.resolver.extraNodeModules = {
|
|
73
|
+
crypto: require.resolve('expo-crypto'),
|
|
74
|
+
stream: require.resolve('readable-stream'),
|
|
75
|
+
'react': path.resolve(projectRoot, 'node_modules/react'),
|
|
76
|
+
'react-native': path.resolve(projectRoot, 'node_modules/react-native'),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
module.exports = config;
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Usage
|
|
83
|
+
|
|
84
|
+
### 1. Wrap Your App
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import { PrivyProvider } from '@privy-io/expo';
|
|
88
|
+
import { StarknetProvider } from '@keep-starknet-strange/privy-starknet-provider';
|
|
89
|
+
|
|
90
|
+
export default function App() {
|
|
91
|
+
return (
|
|
92
|
+
<PrivyProvider appId="your-privy-app-id" clientId="your-privy-client-id">
|
|
93
|
+
<StarknetProvider
|
|
94
|
+
config={{
|
|
95
|
+
rpcUrl: 'https://starknet-sepolia.g.alchemy.com/v2/YOUR_KEY',
|
|
96
|
+
contractAddress: '0x...', // Optional
|
|
97
|
+
avnuApiKey: 'your-avnu-api-key', // Required for gasless transactions
|
|
98
|
+
}}
|
|
99
|
+
>
|
|
100
|
+
<YourApp />
|
|
101
|
+
</StarknetProvider>
|
|
102
|
+
</PrivyProvider>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 2. Use the Hook
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import { useStarknet } from '@keep-starknet-strange/privy-starknet-provider';
|
|
111
|
+
|
|
112
|
+
function MyComponent() {
|
|
113
|
+
const {
|
|
114
|
+
address,
|
|
115
|
+
balance,
|
|
116
|
+
isDeployed,
|
|
117
|
+
txPending,
|
|
118
|
+
executeGaslessTransaction,
|
|
119
|
+
} = useStarknet();
|
|
120
|
+
|
|
121
|
+
// Execute gasless transaction
|
|
122
|
+
const handleTransaction = async () => {
|
|
123
|
+
const result = await executeGaslessTransaction([
|
|
124
|
+
{
|
|
125
|
+
contractAddress: '0x...',
|
|
126
|
+
entrypoint: 'increment',
|
|
127
|
+
calldata: [],
|
|
128
|
+
},
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
if (result.success) {
|
|
132
|
+
console.log('Transaction successful!', result.transactionHash);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<View>
|
|
138
|
+
<Text>Address: {address}</Text>
|
|
139
|
+
<Text>ETH Balance: {balance?.eth}</Text>
|
|
140
|
+
<Text>STRK Balance: {balance?.strk}</Text>
|
|
141
|
+
<Text>Deployed: {isDeployed ? 'Yes' : 'No'}</Text>
|
|
142
|
+
|
|
143
|
+
<Button
|
|
144
|
+
onPress={handleTransaction}
|
|
145
|
+
disabled={txPending}
|
|
146
|
+
title="Execute Transaction"
|
|
147
|
+
/>
|
|
148
|
+
</View>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## API Reference
|
|
154
|
+
|
|
155
|
+
### StarknetProvider
|
|
156
|
+
|
|
157
|
+
Context provider that wraps your app.
|
|
158
|
+
|
|
159
|
+
**Props:**
|
|
160
|
+
|
|
161
|
+
- `config` - Configuration object
|
|
162
|
+
- `rpcUrl` (required) - Starknet RPC endpoint URL
|
|
163
|
+
- `contractAddress` (optional) - Your smart contract address
|
|
164
|
+
- `avnuApiKey` (required) - AVNU API key for gasless transactions
|
|
165
|
+
|
|
166
|
+
### useStarknet()
|
|
167
|
+
|
|
168
|
+
Hook to access Starknet wallet functionality.
|
|
169
|
+
|
|
170
|
+
**Returns:**
|
|
171
|
+
|
|
172
|
+
- `account` - Starknet account instance
|
|
173
|
+
- `provider` - Starknet RPC provider
|
|
174
|
+
- `address` - Account address (0x...)
|
|
175
|
+
- `privateKey` - Private key for signing
|
|
176
|
+
- `balance` - Balance info `{ eth: string, strk: string }`
|
|
177
|
+
- `isDeployed` - Whether account is deployed on-chain
|
|
178
|
+
- `isInitializing` - Whether initialization is in progress
|
|
179
|
+
- `txPending` - Whether a transaction is pending
|
|
180
|
+
- `error` - Current error message
|
|
181
|
+
- `initialize()` - Initialize Starknet account
|
|
182
|
+
- `refreshBalance()` - Refresh balance information
|
|
183
|
+
- `deployAccount()` - Deploy account on-chain (optional, can be done via gasless transaction)
|
|
184
|
+
- `executeGaslessTransaction(calls)` - Execute gasless transaction via AVNU paymaster
|
|
185
|
+
- `clearError()` - Clear error state
|
|
186
|
+
|
|
187
|
+
## Configuration
|
|
188
|
+
|
|
189
|
+
### Get API Keys
|
|
190
|
+
|
|
191
|
+
1. **Privy App ID & Client ID** - Create an app at https://dashboard.privy.io
|
|
192
|
+
2. **AVNU API Key** - Request at https://docs.avnu.fi
|
|
193
|
+
3. **Alchemy RPC** - Get key at https://alchemy.com (or use any Starknet RPC)
|
|
194
|
+
|
|
195
|
+
### Environment Variables
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
EXPO_PUBLIC_PRIVY_APP_ID=your_privy_app_id
|
|
199
|
+
EXPO_PUBLIC_PRIVY_CLIENT_ID=your_privy_client_id
|
|
200
|
+
EXPO_PUBLIC_AVNU_API_KEY=your_avnu_api_key
|
|
201
|
+
EXPO_PUBLIC_STARKNET_RPC_URL=https://starknet-sepolia.g.alchemy.com/v2/YOUR_KEY
|
|
202
|
+
EXPO_PUBLIC_CONTRACT_ADDRESS=0x...
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Example
|
|
206
|
+
|
|
207
|
+
See the `example` directory for a complete working demo.
|
|
208
|
+
|
|
209
|
+
To run the example:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
cd example
|
|
213
|
+
npm install
|
|
214
|
+
npx expo start
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Account Model
|
|
218
|
+
|
|
219
|
+
This package uses ArgentX account contracts on Starknet Sepolia testnet:
|
|
220
|
+
|
|
221
|
+
- Deterministic address derivation from Privy user ID
|
|
222
|
+
- Same user always gets same Starknet address
|
|
223
|
+
- Account can be deployed automatically via gasless transactions
|
|
224
|
+
- Uses STRK as gas token when deployed
|
|
225
|
+
|
|
226
|
+
## Gasless Transactions
|
|
227
|
+
|
|
228
|
+
Gasless transactions are powered by AVNU paymaster:
|
|
229
|
+
|
|
230
|
+
- Works for both deployed and undeployed accounts
|
|
231
|
+
- Undeployed accounts can deploy and execute in one gasless transaction
|
|
232
|
+
- Deployed accounts use STRK as gas token (sponsored by AVNU)
|
|
233
|
+
- Requires valid AVNU API key
|
|
234
|
+
- Works on Starknet Sepolia testnet
|
|
235
|
+
|
|
236
|
+
## License
|
|
237
|
+
|
|
238
|
+
MIT
|
|
239
|
+
|
|
240
|
+
## Contributing
|
|
241
|
+
|
|
242
|
+
Contributions welcome! Please open an issue or PR on GitHub.
|
|
243
|
+
|
|
244
|
+
## Credits
|
|
245
|
+
|
|
246
|
+
Built with:
|
|
247
|
+
|
|
248
|
+
- Privy - Embedded wallet infrastructure
|
|
249
|
+
- Starknet.js - Starknet JavaScript library
|
|
250
|
+
- AVNU - Gasless transaction paymaster
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
import type { StarknetContext as StarknetContext0, StarknetProviderConfig } from './types';
|
|
3
|
+
export declare const StarknetContext: React.Context<StarknetContext0 | null>;
|
|
4
|
+
interface StarknetProviderProps {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
config: StarknetProviderConfig;
|
|
7
|
+
}
|
|
8
|
+
export declare function StarknetProvider({ children, config }: StarknetProviderProps): React.JSX.Element;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=StarknetProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StarknetProvider.d.ts","sourceRoot":"","sources":["../src/StarknetProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAsC,SAAS,EAAE,MAAM,OAAO,CAAC;AAG7E,OAAO,KAAK,EACV,eAAe,IAAI,gBAAgB,EACnC,sBAAsB,EAGvB,MAAM,SAAS,CAAC;AAWjB,eAAO,MAAM,eAAe,wCAA+C,CAAC;AAE5E,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,SAAS,CAAC;IACpB,MAAM,EAAE,sBAAsB,CAAC;CAChC;AAED,wBAAgB,gBAAgB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,qBAAqB,qBA+Q3E"}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.StarknetContext = void 0;
|
|
37
|
+
exports.StarknetProvider = StarknetProvider;
|
|
38
|
+
const react_1 = __importStar(require("react"));
|
|
39
|
+
const expo_1 = require("@privy-io/expo");
|
|
40
|
+
const starknet_1 = require("starknet");
|
|
41
|
+
const utils_1 = require("./utils");
|
|
42
|
+
exports.StarknetContext = (0, react_1.createContext)(null);
|
|
43
|
+
function StarknetProvider({ children, config }) {
|
|
44
|
+
const { user } = (0, expo_1.usePrivy)();
|
|
45
|
+
const [account, setAccount] = (0, react_1.useState)(null);
|
|
46
|
+
const [provider, setProvider] = (0, react_1.useState)(null);
|
|
47
|
+
const [address, setAddress] = (0, react_1.useState)(null);
|
|
48
|
+
const [privateKey, setPrivateKey] = (0, react_1.useState)(null);
|
|
49
|
+
const [balance, setBalance] = (0, react_1.useState)(null);
|
|
50
|
+
const [isDeployed, setIsDeployed] = (0, react_1.useState)(false);
|
|
51
|
+
const [isInitializing, setIsInitializing] = (0, react_1.useState)(false);
|
|
52
|
+
const [txPending, setTxPending] = (0, react_1.useState)(false);
|
|
53
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
54
|
+
// Initialize Starknet account when Privy user is logged in
|
|
55
|
+
(0, react_1.useEffect)(() => {
|
|
56
|
+
if (user) {
|
|
57
|
+
initialize();
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// Reset state when user logs out
|
|
61
|
+
setAccount(null);
|
|
62
|
+
setProvider(null);
|
|
63
|
+
setAddress(null);
|
|
64
|
+
setPrivateKey(null);
|
|
65
|
+
setBalance(null);
|
|
66
|
+
setIsDeployed(false);
|
|
67
|
+
setError(null);
|
|
68
|
+
}
|
|
69
|
+
}, [user]);
|
|
70
|
+
// Auto-refresh balance every 10 seconds when account exists
|
|
71
|
+
(0, react_1.useEffect)(() => {
|
|
72
|
+
if (account && provider) {
|
|
73
|
+
const interval = setInterval(() => {
|
|
74
|
+
refreshBalance();
|
|
75
|
+
}, 10000);
|
|
76
|
+
return () => clearInterval(interval);
|
|
77
|
+
}
|
|
78
|
+
}, [account, provider]);
|
|
79
|
+
const initialize = async () => {
|
|
80
|
+
if (!user || !config.rpcUrl) {
|
|
81
|
+
setError('Missing user or RPC URL');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
setIsInitializing(true);
|
|
85
|
+
setError(null);
|
|
86
|
+
try {
|
|
87
|
+
// Create Starknet provider
|
|
88
|
+
const starknetProvider = (0, utils_1.createStarknetProvider)(config.rpcUrl);
|
|
89
|
+
// Derive deterministic private key from Privy user ID
|
|
90
|
+
const userSeed = user.id;
|
|
91
|
+
const derivedPrivateKey = await (0, utils_1.derivePrivateKey)(userSeed);
|
|
92
|
+
// Create Starknet account
|
|
93
|
+
const starknetAccount = (0, utils_1.createStarknetAccount)(derivedPrivateKey, starknetProvider);
|
|
94
|
+
// Set state
|
|
95
|
+
setProvider(starknetProvider);
|
|
96
|
+
setAccount(starknetAccount);
|
|
97
|
+
setAddress(starknetAccount.address);
|
|
98
|
+
setPrivateKey(derivedPrivateKey);
|
|
99
|
+
// Fetch initial balance and deployment status
|
|
100
|
+
const balances = await (0, utils_1.fetchBalances)(starknetProvider, starknetAccount.address);
|
|
101
|
+
setBalance(balances);
|
|
102
|
+
const deploymentStatus = await (0, utils_1.checkAccountDeployment)(starknetProvider, starknetAccount.address);
|
|
103
|
+
setIsDeployed(deploymentStatus.isDeployed);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
console.error('Failed to initialize Starknet account:', err);
|
|
107
|
+
setError(err.message || 'Failed to initialize Starknet account');
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
setIsInitializing(false);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const refreshBalance = async () => {
|
|
114
|
+
if (!provider || !address)
|
|
115
|
+
return;
|
|
116
|
+
try {
|
|
117
|
+
const balances = await (0, utils_1.fetchBalances)(provider, address);
|
|
118
|
+
setBalance(balances);
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
console.error('Failed to refresh balance:', err);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const deployAccount = async () => {
|
|
125
|
+
if (!account || !provider || !privateKey) {
|
|
126
|
+
return { transactionHash: '', success: false, error: 'Account not initialized' };
|
|
127
|
+
}
|
|
128
|
+
setTxPending(true);
|
|
129
|
+
setError(null);
|
|
130
|
+
try {
|
|
131
|
+
console.log('🚀 Deploying Starknet account...');
|
|
132
|
+
const starkKeyPub = starknet_1.ec.starkCurve.getStarkKey(privateKey);
|
|
133
|
+
const constructorCalldata = starknet_1.CallData.compile({
|
|
134
|
+
owner: starkKeyPub,
|
|
135
|
+
guardian: '0x0',
|
|
136
|
+
});
|
|
137
|
+
const { transaction_hash } = await account.deployAccount({
|
|
138
|
+
classHash: utils_1.ARGENTX_CLASS_HASH,
|
|
139
|
+
constructorCalldata,
|
|
140
|
+
addressSalt: starkKeyPub,
|
|
141
|
+
contractAddress: account.address,
|
|
142
|
+
});
|
|
143
|
+
console.log('📡 Waiting for deployment confirmation...');
|
|
144
|
+
await provider.waitForTransaction(transaction_hash);
|
|
145
|
+
console.log('✅ Account deployed successfully!');
|
|
146
|
+
setIsDeployed(true);
|
|
147
|
+
// Refresh balance after deployment
|
|
148
|
+
await refreshBalance();
|
|
149
|
+
return { transactionHash: transaction_hash, success: true };
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
const errorMsg = err.message || 'Failed to deploy account';
|
|
153
|
+
console.error('Deployment failed:', errorMsg);
|
|
154
|
+
setError(errorMsg);
|
|
155
|
+
return { transactionHash: '', success: false, error: errorMsg };
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
setTxPending(false);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const executeGaslessTransaction = async (calls) => {
|
|
162
|
+
if (!account || !provider || !privateKey) {
|
|
163
|
+
return {
|
|
164
|
+
transactionHash: '',
|
|
165
|
+
success: false,
|
|
166
|
+
error: 'Account not initialized',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (!config.avnuApiKey) {
|
|
170
|
+
return {
|
|
171
|
+
transactionHash: '',
|
|
172
|
+
success: false,
|
|
173
|
+
error: 'AVNU API key is required. Please add EXPO_PUBLIC_AVNU_API_KEY to your .env file.',
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
setTxPending(true);
|
|
177
|
+
setError(null);
|
|
178
|
+
try {
|
|
179
|
+
console.log('🚀 Executing gasless transaction with AVNU Paymaster...');
|
|
180
|
+
console.log('Account address:', account.address);
|
|
181
|
+
// Check if account is deployed
|
|
182
|
+
const deploymentStatus = await (0, utils_1.checkAccountDeployment)(provider, account.address);
|
|
183
|
+
console.log('Account deployed:', deploymentStatus.isDeployed);
|
|
184
|
+
// Create account with paymaster configured
|
|
185
|
+
const paymasterOptions = {
|
|
186
|
+
nodeUrl: 'https://sepolia.paymaster.avnu.fi',
|
|
187
|
+
headers: {
|
|
188
|
+
'x-paymaster-api-key': config.avnuApiKey,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
// Create new account instance with paymaster
|
|
192
|
+
const paymasterAccount = new starknet_1.Account({
|
|
193
|
+
provider,
|
|
194
|
+
address: account.address,
|
|
195
|
+
signer: privateKey,
|
|
196
|
+
paymaster: paymasterOptions,
|
|
197
|
+
});
|
|
198
|
+
// Prepare paymaster details
|
|
199
|
+
// Use 'sponsored' mode so paymaster pays for all gas fees
|
|
200
|
+
const paymasterDetails = {
|
|
201
|
+
feeMode: { mode: 'sponsored' },
|
|
202
|
+
};
|
|
203
|
+
if (!deploymentStatus.isDeployed) {
|
|
204
|
+
const deploymentData = (0, utils_1.generateDeploymentData)(privateKey);
|
|
205
|
+
paymasterDetails.deploymentData = {
|
|
206
|
+
address: account.address,
|
|
207
|
+
class_hash: deploymentData.class_hash,
|
|
208
|
+
salt: deploymentData.salt,
|
|
209
|
+
unique: deploymentData.unique,
|
|
210
|
+
calldata: deploymentData.calldata,
|
|
211
|
+
version: 1,
|
|
212
|
+
};
|
|
213
|
+
console.log('Including deployment data for undeployed account (sponsored mode)');
|
|
214
|
+
}
|
|
215
|
+
console.log('Estimating fees with paymaster...');
|
|
216
|
+
const estimatedFees = await paymasterAccount.estimatePaymasterTransactionFee(calls, paymasterDetails);
|
|
217
|
+
console.log('Estimated fees:', estimatedFees);
|
|
218
|
+
console.log('Executing transaction...');
|
|
219
|
+
const result = await paymasterAccount.executePaymasterTransaction(calls, paymasterDetails, estimatedFees.suggested_max_fee_in_gas_token);
|
|
220
|
+
console.log('📡 Waiting for transaction confirmation...');
|
|
221
|
+
await provider.waitForTransaction(result.transaction_hash);
|
|
222
|
+
console.log('✅ Gasless transaction confirmed!');
|
|
223
|
+
// Update deployment status if account wasn't deployed
|
|
224
|
+
if (!deploymentStatus.isDeployed) {
|
|
225
|
+
setIsDeployed(true);
|
|
226
|
+
}
|
|
227
|
+
// Refresh balance after transaction
|
|
228
|
+
await refreshBalance();
|
|
229
|
+
return { transactionHash: result.transaction_hash, success: true };
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
console.error('Paymaster error:', err);
|
|
233
|
+
console.error('Error message:', err.message);
|
|
234
|
+
let errorMsg = err.message || 'Gasless transaction failed';
|
|
235
|
+
// Provide helpful error messages
|
|
236
|
+
if (errorMsg.includes('401')) {
|
|
237
|
+
errorMsg = 'AVNU API key is invalid or expired. Get a new key at https://docs.avnu.fi';
|
|
238
|
+
}
|
|
239
|
+
else if (errorMsg.includes('403')) {
|
|
240
|
+
errorMsg = 'AVNU API key does not have permission for paymaster transactions';
|
|
241
|
+
}
|
|
242
|
+
else if (errorMsg.includes('429')) {
|
|
243
|
+
errorMsg = 'AVNU rate limit exceeded. Please try again later.';
|
|
244
|
+
}
|
|
245
|
+
console.error('Gasless transaction error:', errorMsg);
|
|
246
|
+
setError(errorMsg);
|
|
247
|
+
return { transactionHash: '', success: false, error: errorMsg };
|
|
248
|
+
}
|
|
249
|
+
finally {
|
|
250
|
+
setTxPending(false);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
const clearError = () => {
|
|
254
|
+
setError(null);
|
|
255
|
+
};
|
|
256
|
+
const contextValue = {
|
|
257
|
+
account,
|
|
258
|
+
provider,
|
|
259
|
+
address,
|
|
260
|
+
privateKey,
|
|
261
|
+
balance,
|
|
262
|
+
isDeployed,
|
|
263
|
+
isInitializing,
|
|
264
|
+
txPending,
|
|
265
|
+
error,
|
|
266
|
+
initialize,
|
|
267
|
+
refreshBalance,
|
|
268
|
+
deployAccount,
|
|
269
|
+
executeGaslessTransaction,
|
|
270
|
+
clearError,
|
|
271
|
+
};
|
|
272
|
+
return <exports.StarknetContext.Provider value={contextValue}>{children}</exports.StarknetContext.Provider>;
|
|
273
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { StarknetProvider } from './StarknetProvider';
|
|
2
|
+
export { useStarknet } from './useStarknet';
|
|
3
|
+
export type { StarknetProviderConfig, BalanceInfo, DeploymentStatus, TransactionResult, StarknetContextState, StarknetContextActions, StarknetContext, } from './types';
|
|
4
|
+
export { derivePrivateKey, createStarknetAccount, createStarknetProvider, fetchBalances, checkAccountDeployment, generateDeploymentData, ETH_TOKEN_ADDRESS, STRK_TOKEN_ADDRESS, ARGENTX_CLASS_HASH, } from './utils';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C,YAAY,EACV,sBAAsB,EACtB,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACtB,eAAe,GAChB,MAAM,SAAS,CAAC;AAGjB,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,EACtB,aAAa,EACb,sBAAsB,EACtB,sBAAsB,EACtB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ARGENTX_CLASS_HASH = exports.STRK_TOKEN_ADDRESS = exports.ETH_TOKEN_ADDRESS = exports.generateDeploymentData = exports.checkAccountDeployment = exports.fetchBalances = exports.createStarknetProvider = exports.createStarknetAccount = exports.derivePrivateKey = exports.useStarknet = exports.StarknetProvider = void 0;
|
|
4
|
+
// Main exports
|
|
5
|
+
var StarknetProvider_1 = require("./StarknetProvider");
|
|
6
|
+
Object.defineProperty(exports, "StarknetProvider", { enumerable: true, get: function () { return StarknetProvider_1.StarknetProvider; } });
|
|
7
|
+
var useStarknet_1 = require("./useStarknet");
|
|
8
|
+
Object.defineProperty(exports, "useStarknet", { enumerable: true, get: function () { return useStarknet_1.useStarknet; } });
|
|
9
|
+
// Utility exports
|
|
10
|
+
var utils_1 = require("./utils");
|
|
11
|
+
Object.defineProperty(exports, "derivePrivateKey", { enumerable: true, get: function () { return utils_1.derivePrivateKey; } });
|
|
12
|
+
Object.defineProperty(exports, "createStarknetAccount", { enumerable: true, get: function () { return utils_1.createStarknetAccount; } });
|
|
13
|
+
Object.defineProperty(exports, "createStarknetProvider", { enumerable: true, get: function () { return utils_1.createStarknetProvider; } });
|
|
14
|
+
Object.defineProperty(exports, "fetchBalances", { enumerable: true, get: function () { return utils_1.fetchBalances; } });
|
|
15
|
+
Object.defineProperty(exports, "checkAccountDeployment", { enumerable: true, get: function () { return utils_1.checkAccountDeployment; } });
|
|
16
|
+
Object.defineProperty(exports, "generateDeploymentData", { enumerable: true, get: function () { return utils_1.generateDeploymentData; } });
|
|
17
|
+
Object.defineProperty(exports, "ETH_TOKEN_ADDRESS", { enumerable: true, get: function () { return utils_1.ETH_TOKEN_ADDRESS; } });
|
|
18
|
+
Object.defineProperty(exports, "STRK_TOKEN_ADDRESS", { enumerable: true, get: function () { return utils_1.STRK_TOKEN_ADDRESS; } });
|
|
19
|
+
Object.defineProperty(exports, "ARGENTX_CLASS_HASH", { enumerable: true, get: function () { return utils_1.ARGENTX_CLASS_HASH; } });
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "privy-starknet-provider",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Starknet wallet provider for React Native apps using Privy authentication and AVNU paymaster",
|
|
5
|
+
"main": "./index.js",
|
|
6
|
+
"types": "./index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"prepublishOnly": "npm run build",
|
|
10
|
+
"example": "cd example && npm start",
|
|
11
|
+
"example:ios": "cd example && npx expo run:ios --device",
|
|
12
|
+
"example:android": "cd example && npx expo run:android --device"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"starknet",
|
|
16
|
+
"privy",
|
|
17
|
+
"wallet",
|
|
18
|
+
"react-native",
|
|
19
|
+
"expo",
|
|
20
|
+
"avnu",
|
|
21
|
+
"paymaster",
|
|
22
|
+
"gasless",
|
|
23
|
+
"blockchain",
|
|
24
|
+
"web3"
|
|
25
|
+
],
|
|
26
|
+
"author": "Keep Starknet Strange",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/keep-starknet-strange/privy.git"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"react": ">=18.0.0",
|
|
34
|
+
"react-native": ">=0.70.0",
|
|
35
|
+
"expo": ">=49.0.0",
|
|
36
|
+
"@privy-io/expo": ">=0.63.0",
|
|
37
|
+
"expo-crypto": ">=13.0.0"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"starknet": "^9.2.1",
|
|
41
|
+
"@noble/curves": "^1.9.1",
|
|
42
|
+
"@noble/hashes": "^1.8.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/react": "~19.1.0",
|
|
46
|
+
"typescript": "~5.9.2"
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"dist",
|
|
50
|
+
"src",
|
|
51
|
+
"README.md",
|
|
52
|
+
"LICENSE"
|
|
53
|
+
]
|
|
54
|
+
}
|