@settlr/sdk 0.1.0 → 0.2.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 +33 -69
- package/dist/index.d.mts +68 -33
- package/dist/index.d.ts +68 -33
- package/dist/index.js +61 -62
- package/dist/index.mjs +61 -62
- package/package.json +5 -6
package/README.md
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
# @settlr/sdk
|
|
2
2
|
|
|
3
|
-
> Solana USDC payments
|
|
3
|
+
> Solana USDC payments for games - email checkout, no wallet required
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install @settlr/sdk
|
|
9
|
-
# or
|
|
10
|
-
yarn add @settlr/sdk
|
|
11
|
-
# or
|
|
12
|
-
pnpm add @settlr/sdk
|
|
13
9
|
```
|
|
14
10
|
|
|
15
11
|
## Quick Start
|
|
16
12
|
|
|
17
13
|
### 1. Get Your API Key
|
|
18
14
|
|
|
19
|
-
Sign up at [settlr.
|
|
15
|
+
Sign up at [settlr.app/dashboard](https://settlr.app/dashboard) and create an API key.
|
|
20
16
|
|
|
21
17
|
### 2. Create a Payment Link
|
|
22
18
|
|
|
@@ -87,97 +83,65 @@ import { CheckoutWidget } from "@settlr/sdk";
|
|
|
87
83
|
/>;
|
|
88
84
|
```
|
|
89
85
|
|
|
90
|
-
###
|
|
86
|
+
### How It Works
|
|
91
87
|
|
|
92
|
-
|
|
93
|
-
import { Settlr } from "@settlr/sdk";
|
|
94
|
-
import { useWallet } from "@solana/wallet-adapter-react";
|
|
88
|
+
Settlr checkout handles authentication via Privy:
|
|
95
89
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
merchant: {
|
|
99
|
-
name: "My Store",
|
|
100
|
-
walletAddress: "YOUR_WALLET",
|
|
101
|
-
},
|
|
102
|
-
});
|
|
90
|
+
- **Email login** → Creates embedded Solana wallet automatically
|
|
91
|
+
- **Wallet login** → Connects Phantom, Solflare, or Backpack
|
|
103
92
|
|
|
104
|
-
|
|
105
|
-
const wallet = useWallet();
|
|
106
|
-
|
|
107
|
-
const result = await settlr.pay({
|
|
108
|
-
wallet: {
|
|
109
|
-
publicKey: wallet.publicKey!,
|
|
110
|
-
signTransaction: wallet.signTransaction!,
|
|
111
|
-
},
|
|
112
|
-
amount: 29.99,
|
|
113
|
-
memo: "Order #1234",
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
if (result.success) {
|
|
117
|
-
console.log("Payment successful!", result.signature);
|
|
118
|
-
}
|
|
119
|
-
```
|
|
93
|
+
No wallet-adapter setup needed. Just redirect to checkout.
|
|
120
94
|
|
|
121
95
|
### React Hook
|
|
122
96
|
|
|
123
97
|
```tsx
|
|
124
98
|
import { SettlrProvider, useSettlr } from "@settlr/sdk";
|
|
125
99
|
|
|
126
|
-
// Wrap your app
|
|
127
100
|
function App() {
|
|
128
101
|
return (
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
<YourApp />
|
|
141
|
-
</SettlrProvider>
|
|
142
|
-
</ConnectionProvider>
|
|
143
|
-
</WalletProvider>
|
|
102
|
+
<SettlrProvider
|
|
103
|
+
config={{
|
|
104
|
+
apiKey: "sk_live_xxxxxxxxxxxx",
|
|
105
|
+
merchant: {
|
|
106
|
+
name: "My Game",
|
|
107
|
+
walletAddress: "YOUR_WALLET",
|
|
108
|
+
},
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
<YourApp />
|
|
112
|
+
</SettlrProvider>
|
|
144
113
|
);
|
|
145
114
|
}
|
|
146
115
|
|
|
147
116
|
// In your component
|
|
148
117
|
function CheckoutButton() {
|
|
149
|
-
const {
|
|
118
|
+
const { getCheckoutUrl } = useSettlr();
|
|
150
119
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
120
|
+
const handlePay = () => {
|
|
121
|
+
const url = getCheckoutUrl({ amount: 29.99, memo: "Premium Pack" });
|
|
122
|
+
window.location.href = url;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return <button onClick={handlePay}>Pay $29.99</button>;
|
|
156
126
|
}
|
|
157
127
|
```
|
|
158
128
|
|
|
159
|
-
### Payment Link Generator Hook
|
|
129
|
+
### Payment Link Generator Hook
|
|
160
130
|
|
|
161
131
|
Generate shareable payment links programmatically:
|
|
162
132
|
|
|
163
133
|
```tsx
|
|
164
|
-
import {
|
|
134
|
+
import { useSettlr } from "@settlr/sdk";
|
|
165
135
|
|
|
166
136
|
function InvoicePage() {
|
|
167
|
-
const {
|
|
168
|
-
merchantWallet: "YOUR_WALLET",
|
|
169
|
-
merchantName: "My Store",
|
|
170
|
-
});
|
|
137
|
+
const { getCheckoutUrl } = useSettlr();
|
|
171
138
|
|
|
172
|
-
const link =
|
|
139
|
+
const link = getCheckoutUrl({
|
|
173
140
|
amount: 500,
|
|
174
141
|
memo: "Invoice #1234",
|
|
175
142
|
orderId: "inv_1234",
|
|
176
143
|
});
|
|
177
|
-
// → https://settlr.
|
|
178
|
-
|
|
179
|
-
const qrCode = await generateQRCode({ amount: 500 });
|
|
180
|
-
// → QR code image URL
|
|
144
|
+
// → https://settlr.app/checkout?amount=500&merchant=My+Game&...
|
|
181
145
|
}
|
|
182
146
|
```
|
|
183
147
|
|
|
@@ -250,7 +214,7 @@ Full checkout UI component with product info.
|
|
|
250
214
|
|
|
251
215
|
### Get Your API Key
|
|
252
216
|
|
|
253
|
-
1. Go to [settlr.
|
|
217
|
+
1. Go to [settlr.app/dashboard](https://settlr.app/dashboard)
|
|
254
218
|
2. Connect your wallet
|
|
255
219
|
3. Click "Create API Key"
|
|
256
220
|
4. Save the key securely (only shown once!)
|
|
@@ -298,7 +262,7 @@ const payment = await settlr.createPayment({
|
|
|
298
262
|
id: 'pay_abc123',
|
|
299
263
|
amount: 29.99,
|
|
300
264
|
status: 'pending',
|
|
301
|
-
checkoutUrl: 'https://settlr.
|
|
265
|
+
checkoutUrl: 'https://settlr.app/checkout?...',
|
|
302
266
|
qrCode: 'data:image/svg+xml,...',
|
|
303
267
|
createdAt: Date,
|
|
304
268
|
expiresAt: Date,
|
|
@@ -372,7 +336,7 @@ window.location.href = session.url;
|
|
|
372
336
|
// Returns
|
|
373
337
|
{
|
|
374
338
|
id: 'cs_abc123...',
|
|
375
|
-
url: 'https://settlr.
|
|
339
|
+
url: 'https://settlr.app/checkout/cs_abc123...',
|
|
376
340
|
expiresAt: 1702659600000, // 30 min expiry
|
|
377
341
|
}
|
|
378
342
|
```
|
package/dist/index.d.mts
CHANGED
|
@@ -5,8 +5,8 @@ import { ReactNode, CSSProperties } from 'react';
|
|
|
5
5
|
declare const USDC_MINT_DEVNET: PublicKey;
|
|
6
6
|
declare const USDC_MINT_MAINNET: PublicKey;
|
|
7
7
|
declare const SETTLR_CHECKOUT_URL: {
|
|
8
|
-
readonly production: "https://settlr.dev/
|
|
9
|
-
readonly development: "http://localhost:3000/
|
|
8
|
+
readonly production: "https://settlr.dev/checkout";
|
|
9
|
+
readonly development: "http://localhost:3000/checkout";
|
|
10
10
|
};
|
|
11
11
|
declare const SUPPORTED_NETWORKS: readonly ["devnet", "mainnet-beta"];
|
|
12
12
|
type SupportedNetwork = typeof SUPPORTED_NETWORKS[number];
|
|
@@ -182,6 +182,30 @@ declare class Settlr {
|
|
|
182
182
|
* Get the current tier
|
|
183
183
|
*/
|
|
184
184
|
getTier(): 'free' | 'pro' | 'enterprise' | undefined;
|
|
185
|
+
/**
|
|
186
|
+
* Get a checkout URL for redirect-based payments
|
|
187
|
+
*
|
|
188
|
+
* This is the simplest integration - just redirect users to this URL.
|
|
189
|
+
* Settlr handles auth (email or wallet) and payment processing.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* const url = settlr.getCheckoutUrl({
|
|
194
|
+
* amount: 29.99,
|
|
195
|
+
* memo: 'Premium Pack',
|
|
196
|
+
* });
|
|
197
|
+
*
|
|
198
|
+
* // Redirect user to checkout
|
|
199
|
+
* window.location.href = url;
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
getCheckoutUrl(options: {
|
|
203
|
+
amount: number;
|
|
204
|
+
memo?: string;
|
|
205
|
+
orderId?: string;
|
|
206
|
+
successUrl?: string;
|
|
207
|
+
cancelUrl?: string;
|
|
208
|
+
}): string;
|
|
185
209
|
/**
|
|
186
210
|
* Create a payment link
|
|
187
211
|
*
|
|
@@ -314,21 +338,28 @@ declare function parseUSDC(amount: number | string): bigint;
|
|
|
314
338
|
*/
|
|
315
339
|
declare function shortenAddress(address: string, chars?: number): string;
|
|
316
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Checkout URL options
|
|
343
|
+
*/
|
|
344
|
+
interface CheckoutUrlOptions {
|
|
345
|
+
amount: number;
|
|
346
|
+
memo?: string;
|
|
347
|
+
orderId?: string;
|
|
348
|
+
successUrl?: string;
|
|
349
|
+
cancelUrl?: string;
|
|
350
|
+
}
|
|
317
351
|
/**
|
|
318
352
|
* Settlr context value
|
|
319
353
|
*/
|
|
320
354
|
interface SettlrContextValue {
|
|
321
355
|
/** Settlr client instance */
|
|
322
356
|
settlr: Settlr | null;
|
|
323
|
-
/** Whether
|
|
324
|
-
|
|
325
|
-
/** Create a payment link */
|
|
357
|
+
/** Whether user is authenticated */
|
|
358
|
+
authenticated: boolean;
|
|
359
|
+
/** Create a payment link (redirect flow) */
|
|
326
360
|
createPayment: (options: CreatePaymentOptions) => Promise<Payment>;
|
|
327
|
-
/**
|
|
328
|
-
|
|
329
|
-
amount: number;
|
|
330
|
-
memo?: string;
|
|
331
|
-
}) => Promise<PaymentResult>;
|
|
361
|
+
/** Generate checkout URL for redirect */
|
|
362
|
+
getCheckoutUrl: (options: CheckoutUrlOptions) => string;
|
|
332
363
|
/** Get merchant's USDC balance */
|
|
333
364
|
getBalance: () => Promise<number>;
|
|
334
365
|
}
|
|
@@ -337,32 +368,41 @@ interface SettlrContextValue {
|
|
|
337
368
|
*/
|
|
338
369
|
interface SettlrProviderProps {
|
|
339
370
|
children: ReactNode;
|
|
340
|
-
config:
|
|
371
|
+
config: SettlrConfig;
|
|
372
|
+
/** Whether user is authenticated (from Privy or other auth) */
|
|
373
|
+
authenticated?: boolean;
|
|
341
374
|
}
|
|
342
375
|
/**
|
|
343
376
|
* Settlr Provider - Wraps your app to provide Settlr functionality
|
|
344
377
|
*
|
|
378
|
+
* Works with Privy authentication - just pass the authenticated state.
|
|
379
|
+
*
|
|
345
380
|
* @example
|
|
346
381
|
* ```tsx
|
|
347
382
|
* import { SettlrProvider } from '@settlr/sdk';
|
|
383
|
+
* import { usePrivy } from '@privy-io/react-auth';
|
|
348
384
|
*
|
|
349
385
|
* function App() {
|
|
386
|
+
* const { authenticated } = usePrivy();
|
|
387
|
+
*
|
|
350
388
|
* return (
|
|
351
|
-
* <
|
|
352
|
-
*
|
|
389
|
+
* <SettlrProvider
|
|
390
|
+
* authenticated={authenticated}
|
|
391
|
+
* config={{
|
|
392
|
+
* apiKey: 'sk_live_xxxxxxxxxxxx',
|
|
353
393
|
* merchant: {
|
|
354
|
-
* name: 'My
|
|
394
|
+
* name: 'My Game',
|
|
355
395
|
* walletAddress: 'YOUR_WALLET',
|
|
356
396
|
* },
|
|
357
|
-
* }}
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
* </
|
|
397
|
+
* }}
|
|
398
|
+
* >
|
|
399
|
+
* <YourApp />
|
|
400
|
+
* </SettlrProvider>
|
|
361
401
|
* );
|
|
362
402
|
* }
|
|
363
403
|
* ```
|
|
364
404
|
*/
|
|
365
|
-
declare function SettlrProvider({ children, config }: SettlrProviderProps): react_jsx_runtime.JSX.Element;
|
|
405
|
+
declare function SettlrProvider({ children, config, authenticated, }: SettlrProviderProps): react_jsx_runtime.JSX.Element;
|
|
366
406
|
/**
|
|
367
407
|
* useSettlr hook - Access Settlr functionality in your components
|
|
368
408
|
*
|
|
@@ -371,23 +411,17 @@ declare function SettlrProvider({ children, config }: SettlrProviderProps): reac
|
|
|
371
411
|
* import { useSettlr } from '@settlr/sdk';
|
|
372
412
|
*
|
|
373
413
|
* function CheckoutButton() {
|
|
374
|
-
* const {
|
|
375
|
-
*
|
|
376
|
-
* const handlePay = async () => {
|
|
377
|
-
* // Option 1: Create payment link
|
|
378
|
-
* const payment = await createPayment({ amount: 29.99 });
|
|
379
|
-
* window.location.href = payment.checkoutUrl;
|
|
414
|
+
* const { getCheckoutUrl, authenticated } = useSettlr();
|
|
380
415
|
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
* }
|
|
416
|
+
* const handleCheckout = () => {
|
|
417
|
+
* // Redirect to Settlr checkout (handles Privy auth internally)
|
|
418
|
+
* const url = getCheckoutUrl({ amount: 29.99, memo: 'Premium Pack' });
|
|
419
|
+
* window.location.href = url;
|
|
386
420
|
* };
|
|
387
421
|
*
|
|
388
422
|
* return (
|
|
389
|
-
* <button onClick={
|
|
390
|
-
*
|
|
423
|
+
* <button onClick={handleCheckout}>
|
|
424
|
+
* Buy Premium Pack - $29.99
|
|
391
425
|
* </button>
|
|
392
426
|
* );
|
|
393
427
|
* }
|
|
@@ -455,7 +489,8 @@ interface BuyButtonProps {
|
|
|
455
489
|
/** Button size */
|
|
456
490
|
size?: "sm" | "md" | "lg";
|
|
457
491
|
}
|
|
458
|
-
declare function BuyButton({ amount, memo, orderId, children, onSuccess, onError, onProcessing, useRedirect,
|
|
492
|
+
declare function BuyButton({ amount, memo, orderId, children, onSuccess, onError, onProcessing, useRedirect, // Default to redirect flow (works with Privy)
|
|
493
|
+
successUrl, cancelUrl, className, style, disabled, variant, size, }: BuyButtonProps): react_jsx_runtime.JSX.Element;
|
|
459
494
|
/**
|
|
460
495
|
* Checkout Widget - Embeddable checkout form
|
|
461
496
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -5,8 +5,8 @@ import { ReactNode, CSSProperties } from 'react';
|
|
|
5
5
|
declare const USDC_MINT_DEVNET: PublicKey;
|
|
6
6
|
declare const USDC_MINT_MAINNET: PublicKey;
|
|
7
7
|
declare const SETTLR_CHECKOUT_URL: {
|
|
8
|
-
readonly production: "https://settlr.dev/
|
|
9
|
-
readonly development: "http://localhost:3000/
|
|
8
|
+
readonly production: "https://settlr.dev/checkout";
|
|
9
|
+
readonly development: "http://localhost:3000/checkout";
|
|
10
10
|
};
|
|
11
11
|
declare const SUPPORTED_NETWORKS: readonly ["devnet", "mainnet-beta"];
|
|
12
12
|
type SupportedNetwork = typeof SUPPORTED_NETWORKS[number];
|
|
@@ -182,6 +182,30 @@ declare class Settlr {
|
|
|
182
182
|
* Get the current tier
|
|
183
183
|
*/
|
|
184
184
|
getTier(): 'free' | 'pro' | 'enterprise' | undefined;
|
|
185
|
+
/**
|
|
186
|
+
* Get a checkout URL for redirect-based payments
|
|
187
|
+
*
|
|
188
|
+
* This is the simplest integration - just redirect users to this URL.
|
|
189
|
+
* Settlr handles auth (email or wallet) and payment processing.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* const url = settlr.getCheckoutUrl({
|
|
194
|
+
* amount: 29.99,
|
|
195
|
+
* memo: 'Premium Pack',
|
|
196
|
+
* });
|
|
197
|
+
*
|
|
198
|
+
* // Redirect user to checkout
|
|
199
|
+
* window.location.href = url;
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
getCheckoutUrl(options: {
|
|
203
|
+
amount: number;
|
|
204
|
+
memo?: string;
|
|
205
|
+
orderId?: string;
|
|
206
|
+
successUrl?: string;
|
|
207
|
+
cancelUrl?: string;
|
|
208
|
+
}): string;
|
|
185
209
|
/**
|
|
186
210
|
* Create a payment link
|
|
187
211
|
*
|
|
@@ -314,21 +338,28 @@ declare function parseUSDC(amount: number | string): bigint;
|
|
|
314
338
|
*/
|
|
315
339
|
declare function shortenAddress(address: string, chars?: number): string;
|
|
316
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Checkout URL options
|
|
343
|
+
*/
|
|
344
|
+
interface CheckoutUrlOptions {
|
|
345
|
+
amount: number;
|
|
346
|
+
memo?: string;
|
|
347
|
+
orderId?: string;
|
|
348
|
+
successUrl?: string;
|
|
349
|
+
cancelUrl?: string;
|
|
350
|
+
}
|
|
317
351
|
/**
|
|
318
352
|
* Settlr context value
|
|
319
353
|
*/
|
|
320
354
|
interface SettlrContextValue {
|
|
321
355
|
/** Settlr client instance */
|
|
322
356
|
settlr: Settlr | null;
|
|
323
|
-
/** Whether
|
|
324
|
-
|
|
325
|
-
/** Create a payment link */
|
|
357
|
+
/** Whether user is authenticated */
|
|
358
|
+
authenticated: boolean;
|
|
359
|
+
/** Create a payment link (redirect flow) */
|
|
326
360
|
createPayment: (options: CreatePaymentOptions) => Promise<Payment>;
|
|
327
|
-
/**
|
|
328
|
-
|
|
329
|
-
amount: number;
|
|
330
|
-
memo?: string;
|
|
331
|
-
}) => Promise<PaymentResult>;
|
|
361
|
+
/** Generate checkout URL for redirect */
|
|
362
|
+
getCheckoutUrl: (options: CheckoutUrlOptions) => string;
|
|
332
363
|
/** Get merchant's USDC balance */
|
|
333
364
|
getBalance: () => Promise<number>;
|
|
334
365
|
}
|
|
@@ -337,32 +368,41 @@ interface SettlrContextValue {
|
|
|
337
368
|
*/
|
|
338
369
|
interface SettlrProviderProps {
|
|
339
370
|
children: ReactNode;
|
|
340
|
-
config:
|
|
371
|
+
config: SettlrConfig;
|
|
372
|
+
/** Whether user is authenticated (from Privy or other auth) */
|
|
373
|
+
authenticated?: boolean;
|
|
341
374
|
}
|
|
342
375
|
/**
|
|
343
376
|
* Settlr Provider - Wraps your app to provide Settlr functionality
|
|
344
377
|
*
|
|
378
|
+
* Works with Privy authentication - just pass the authenticated state.
|
|
379
|
+
*
|
|
345
380
|
* @example
|
|
346
381
|
* ```tsx
|
|
347
382
|
* import { SettlrProvider } from '@settlr/sdk';
|
|
383
|
+
* import { usePrivy } from '@privy-io/react-auth';
|
|
348
384
|
*
|
|
349
385
|
* function App() {
|
|
386
|
+
* const { authenticated } = usePrivy();
|
|
387
|
+
*
|
|
350
388
|
* return (
|
|
351
|
-
* <
|
|
352
|
-
*
|
|
389
|
+
* <SettlrProvider
|
|
390
|
+
* authenticated={authenticated}
|
|
391
|
+
* config={{
|
|
392
|
+
* apiKey: 'sk_live_xxxxxxxxxxxx',
|
|
353
393
|
* merchant: {
|
|
354
|
-
* name: 'My
|
|
394
|
+
* name: 'My Game',
|
|
355
395
|
* walletAddress: 'YOUR_WALLET',
|
|
356
396
|
* },
|
|
357
|
-
* }}
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
* </
|
|
397
|
+
* }}
|
|
398
|
+
* >
|
|
399
|
+
* <YourApp />
|
|
400
|
+
* </SettlrProvider>
|
|
361
401
|
* );
|
|
362
402
|
* }
|
|
363
403
|
* ```
|
|
364
404
|
*/
|
|
365
|
-
declare function SettlrProvider({ children, config }: SettlrProviderProps): react_jsx_runtime.JSX.Element;
|
|
405
|
+
declare function SettlrProvider({ children, config, authenticated, }: SettlrProviderProps): react_jsx_runtime.JSX.Element;
|
|
366
406
|
/**
|
|
367
407
|
* useSettlr hook - Access Settlr functionality in your components
|
|
368
408
|
*
|
|
@@ -371,23 +411,17 @@ declare function SettlrProvider({ children, config }: SettlrProviderProps): reac
|
|
|
371
411
|
* import { useSettlr } from '@settlr/sdk';
|
|
372
412
|
*
|
|
373
413
|
* function CheckoutButton() {
|
|
374
|
-
* const {
|
|
375
|
-
*
|
|
376
|
-
* const handlePay = async () => {
|
|
377
|
-
* // Option 1: Create payment link
|
|
378
|
-
* const payment = await createPayment({ amount: 29.99 });
|
|
379
|
-
* window.location.href = payment.checkoutUrl;
|
|
414
|
+
* const { getCheckoutUrl, authenticated } = useSettlr();
|
|
380
415
|
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
* }
|
|
416
|
+
* const handleCheckout = () => {
|
|
417
|
+
* // Redirect to Settlr checkout (handles Privy auth internally)
|
|
418
|
+
* const url = getCheckoutUrl({ amount: 29.99, memo: 'Premium Pack' });
|
|
419
|
+
* window.location.href = url;
|
|
386
420
|
* };
|
|
387
421
|
*
|
|
388
422
|
* return (
|
|
389
|
-
* <button onClick={
|
|
390
|
-
*
|
|
423
|
+
* <button onClick={handleCheckout}>
|
|
424
|
+
* Buy Premium Pack - $29.99
|
|
391
425
|
* </button>
|
|
392
426
|
* );
|
|
393
427
|
* }
|
|
@@ -455,7 +489,8 @@ interface BuyButtonProps {
|
|
|
455
489
|
/** Button size */
|
|
456
490
|
size?: "sm" | "md" | "lg";
|
|
457
491
|
}
|
|
458
|
-
declare function BuyButton({ amount, memo, orderId, children, onSuccess, onError, onProcessing, useRedirect,
|
|
492
|
+
declare function BuyButton({ amount, memo, orderId, children, onSuccess, onError, onProcessing, useRedirect, // Default to redirect flow (works with Privy)
|
|
493
|
+
successUrl, cancelUrl, className, style, disabled, variant, size, }: BuyButtonProps): react_jsx_runtime.JSX.Element;
|
|
459
494
|
/**
|
|
460
495
|
* Checkout Widget - Embeddable checkout form
|
|
461
496
|
*
|
package/dist/index.js
CHANGED
|
@@ -62,8 +62,8 @@ var SETTLR_API_URL = {
|
|
|
62
62
|
development: "http://localhost:3000/api"
|
|
63
63
|
};
|
|
64
64
|
var SETTLR_CHECKOUT_URL = {
|
|
65
|
-
production: "https://settlr.dev/
|
|
66
|
-
development: "http://localhost:3000/
|
|
65
|
+
production: "https://settlr.dev/checkout",
|
|
66
|
+
development: "http://localhost:3000/checkout"
|
|
67
67
|
};
|
|
68
68
|
var SUPPORTED_NETWORKS = ["devnet", "mainnet-beta"];
|
|
69
69
|
var USDC_DECIMALS = 6;
|
|
@@ -188,6 +188,37 @@ var Settlr = class {
|
|
|
188
188
|
getTier() {
|
|
189
189
|
return this.tier;
|
|
190
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Get a checkout URL for redirect-based payments
|
|
193
|
+
*
|
|
194
|
+
* This is the simplest integration - just redirect users to this URL.
|
|
195
|
+
* Settlr handles auth (email or wallet) and payment processing.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```typescript
|
|
199
|
+
* const url = settlr.getCheckoutUrl({
|
|
200
|
+
* amount: 29.99,
|
|
201
|
+
* memo: 'Premium Pack',
|
|
202
|
+
* });
|
|
203
|
+
*
|
|
204
|
+
* // Redirect user to checkout
|
|
205
|
+
* window.location.href = url;
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
getCheckoutUrl(options) {
|
|
209
|
+
const { amount, memo, orderId, successUrl, cancelUrl } = options;
|
|
210
|
+
const baseUrl = this.config.testMode ? SETTLR_CHECKOUT_URL.development : SETTLR_CHECKOUT_URL.production;
|
|
211
|
+
const params = new URLSearchParams({
|
|
212
|
+
amount: amount.toString(),
|
|
213
|
+
merchant: this.config.merchant.name,
|
|
214
|
+
to: this.config.merchant.walletAddress
|
|
215
|
+
});
|
|
216
|
+
if (memo) params.set("memo", memo);
|
|
217
|
+
if (orderId) params.set("orderId", orderId);
|
|
218
|
+
if (successUrl) params.set("successUrl", successUrl);
|
|
219
|
+
if (cancelUrl) params.set("cancelUrl", cancelUrl);
|
|
220
|
+
return `${baseUrl}?${params.toString()}`;
|
|
221
|
+
}
|
|
191
222
|
/**
|
|
192
223
|
* Create a payment link
|
|
193
224
|
*
|
|
@@ -221,8 +252,8 @@ var Settlr = class {
|
|
|
221
252
|
});
|
|
222
253
|
if (memo) params.set("memo", memo);
|
|
223
254
|
if (orderId) params.set("orderId", orderId);
|
|
224
|
-
if (successUrl) params.set("
|
|
225
|
-
if (cancelUrl) params.set("
|
|
255
|
+
if (successUrl) params.set("success", successUrl);
|
|
256
|
+
if (cancelUrl) params.set("cancel", cancelUrl);
|
|
226
257
|
if (paymentId) params.set("paymentId", paymentId);
|
|
227
258
|
const checkoutUrl = `${baseUrl}?${params.toString()}`;
|
|
228
259
|
const qrCode = await this.generateQRCode(checkoutUrl);
|
|
@@ -466,49 +497,34 @@ var Settlr = class {
|
|
|
466
497
|
|
|
467
498
|
// src/react.tsx
|
|
468
499
|
var import_react = require("react");
|
|
469
|
-
var import_wallet_adapter_react = require("@solana/wallet-adapter-react");
|
|
470
500
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
471
501
|
var SettlrContext = (0, import_react.createContext)(null);
|
|
472
|
-
function SettlrProvider({
|
|
473
|
-
|
|
474
|
-
|
|
502
|
+
function SettlrProvider({
|
|
503
|
+
children,
|
|
504
|
+
config,
|
|
505
|
+
authenticated = false
|
|
506
|
+
}) {
|
|
475
507
|
const settlr = (0, import_react.useMemo)(() => {
|
|
476
508
|
return new Settlr({
|
|
477
509
|
...config,
|
|
478
|
-
rpcEndpoint:
|
|
510
|
+
rpcEndpoint: config.rpcEndpoint ?? "https://api.devnet.solana.com"
|
|
479
511
|
});
|
|
480
|
-
}, [config
|
|
512
|
+
}, [config]);
|
|
481
513
|
const value = (0, import_react.useMemo)(
|
|
482
514
|
() => ({
|
|
483
515
|
settlr,
|
|
484
|
-
|
|
516
|
+
authenticated,
|
|
485
517
|
createPayment: (options) => {
|
|
486
518
|
return settlr.createPayment(options);
|
|
487
519
|
},
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
return {
|
|
491
|
-
success: false,
|
|
492
|
-
signature: "",
|
|
493
|
-
amount: options.amount,
|
|
494
|
-
merchantAddress: settlr.getMerchantAddress().toBase58(),
|
|
495
|
-
error: "Wallet not connected"
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
return settlr.pay({
|
|
499
|
-
wallet: {
|
|
500
|
-
publicKey: wallet.publicKey,
|
|
501
|
-
signTransaction: wallet.signTransaction
|
|
502
|
-
},
|
|
503
|
-
amount: options.amount,
|
|
504
|
-
memo: options.memo
|
|
505
|
-
});
|
|
520
|
+
getCheckoutUrl: (options) => {
|
|
521
|
+
return settlr.getCheckoutUrl(options);
|
|
506
522
|
},
|
|
507
523
|
getBalance: () => {
|
|
508
524
|
return settlr.getMerchantBalance();
|
|
509
525
|
}
|
|
510
526
|
}),
|
|
511
|
-
[settlr,
|
|
527
|
+
[settlr, authenticated]
|
|
512
528
|
);
|
|
513
529
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettlrContext.Provider, { value, children });
|
|
514
530
|
}
|
|
@@ -578,7 +594,8 @@ function BuyButton({
|
|
|
578
594
|
onSuccess,
|
|
579
595
|
onError,
|
|
580
596
|
onProcessing,
|
|
581
|
-
useRedirect =
|
|
597
|
+
useRedirect = true,
|
|
598
|
+
// Default to redirect flow (works with Privy)
|
|
582
599
|
successUrl,
|
|
583
600
|
cancelUrl,
|
|
584
601
|
className,
|
|
@@ -587,7 +604,7 @@ function BuyButton({
|
|
|
587
604
|
variant = "primary",
|
|
588
605
|
size = "md"
|
|
589
606
|
}) {
|
|
590
|
-
const {
|
|
607
|
+
const { getCheckoutUrl, createPayment } = useSettlr();
|
|
591
608
|
const [loading, setLoading] = (0, import_react2.useState)(false);
|
|
592
609
|
const [status, setStatus] = (0, import_react2.useState)("idle");
|
|
593
610
|
const handleClick = (0, import_react2.useCallback)(async () => {
|
|
@@ -596,32 +613,17 @@ function BuyButton({
|
|
|
596
613
|
setStatus("processing");
|
|
597
614
|
onProcessing?.();
|
|
598
615
|
try {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
window.location.href = payment.checkoutUrl;
|
|
608
|
-
} else {
|
|
609
|
-
const result = await pay({ amount, memo });
|
|
610
|
-
if (result.success) {
|
|
611
|
-
setStatus("success");
|
|
612
|
-
onSuccess?.({
|
|
613
|
-
signature: result.signature,
|
|
614
|
-
amount: result.amount,
|
|
615
|
-
merchantAddress: result.merchantAddress
|
|
616
|
-
});
|
|
617
|
-
} else {
|
|
618
|
-
throw new Error(result.error || "Payment failed");
|
|
619
|
-
}
|
|
620
|
-
}
|
|
616
|
+
const url = getCheckoutUrl({
|
|
617
|
+
amount,
|
|
618
|
+
memo,
|
|
619
|
+
orderId,
|
|
620
|
+
successUrl,
|
|
621
|
+
cancelUrl
|
|
622
|
+
});
|
|
623
|
+
window.location.href = url;
|
|
621
624
|
} catch (error) {
|
|
622
625
|
setStatus("error");
|
|
623
626
|
onError?.(error instanceof Error ? error : new Error("Payment failed"));
|
|
624
|
-
} finally {
|
|
625
627
|
setLoading(false);
|
|
626
628
|
}
|
|
627
629
|
}, [
|
|
@@ -630,12 +632,9 @@ function BuyButton({
|
|
|
630
632
|
orderId,
|
|
631
633
|
disabled,
|
|
632
634
|
loading,
|
|
633
|
-
useRedirect,
|
|
634
635
|
successUrl,
|
|
635
636
|
cancelUrl,
|
|
636
|
-
|
|
637
|
-
createPayment,
|
|
638
|
-
onSuccess,
|
|
637
|
+
getCheckoutUrl,
|
|
639
638
|
onError,
|
|
640
639
|
onProcessing
|
|
641
640
|
]);
|
|
@@ -655,11 +654,11 @@ function BuyButton({
|
|
|
655
654
|
"button",
|
|
656
655
|
{
|
|
657
656
|
onClick: handleClick,
|
|
658
|
-
disabled: disabled || loading
|
|
657
|
+
disabled: disabled || loading,
|
|
659
658
|
className,
|
|
660
659
|
style: buttonStyle,
|
|
661
660
|
type: "button",
|
|
662
|
-
children:
|
|
661
|
+
children: buttonContent
|
|
663
662
|
}
|
|
664
663
|
);
|
|
665
664
|
}
|
|
@@ -783,7 +782,7 @@ function CheckoutWidget({
|
|
|
783
782
|
theme = "dark",
|
|
784
783
|
showBranding = true
|
|
785
784
|
}) {
|
|
786
|
-
const {
|
|
785
|
+
const { getCheckoutUrl } = useSettlr();
|
|
787
786
|
const [status, setStatus] = (0, import_react2.useState)("idle");
|
|
788
787
|
const containerStyle = {
|
|
789
788
|
...widgetStyles.container,
|
package/dist/index.mjs
CHANGED
|
@@ -22,8 +22,8 @@ var SETTLR_API_URL = {
|
|
|
22
22
|
development: "http://localhost:3000/api"
|
|
23
23
|
};
|
|
24
24
|
var SETTLR_CHECKOUT_URL = {
|
|
25
|
-
production: "https://settlr.dev/
|
|
26
|
-
development: "http://localhost:3000/
|
|
25
|
+
production: "https://settlr.dev/checkout",
|
|
26
|
+
development: "http://localhost:3000/checkout"
|
|
27
27
|
};
|
|
28
28
|
var SUPPORTED_NETWORKS = ["devnet", "mainnet-beta"];
|
|
29
29
|
var USDC_DECIMALS = 6;
|
|
@@ -148,6 +148,37 @@ var Settlr = class {
|
|
|
148
148
|
getTier() {
|
|
149
149
|
return this.tier;
|
|
150
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Get a checkout URL for redirect-based payments
|
|
153
|
+
*
|
|
154
|
+
* This is the simplest integration - just redirect users to this URL.
|
|
155
|
+
* Settlr handles auth (email or wallet) and payment processing.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```typescript
|
|
159
|
+
* const url = settlr.getCheckoutUrl({
|
|
160
|
+
* amount: 29.99,
|
|
161
|
+
* memo: 'Premium Pack',
|
|
162
|
+
* });
|
|
163
|
+
*
|
|
164
|
+
* // Redirect user to checkout
|
|
165
|
+
* window.location.href = url;
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
getCheckoutUrl(options) {
|
|
169
|
+
const { amount, memo, orderId, successUrl, cancelUrl } = options;
|
|
170
|
+
const baseUrl = this.config.testMode ? SETTLR_CHECKOUT_URL.development : SETTLR_CHECKOUT_URL.production;
|
|
171
|
+
const params = new URLSearchParams({
|
|
172
|
+
amount: amount.toString(),
|
|
173
|
+
merchant: this.config.merchant.name,
|
|
174
|
+
to: this.config.merchant.walletAddress
|
|
175
|
+
});
|
|
176
|
+
if (memo) params.set("memo", memo);
|
|
177
|
+
if (orderId) params.set("orderId", orderId);
|
|
178
|
+
if (successUrl) params.set("successUrl", successUrl);
|
|
179
|
+
if (cancelUrl) params.set("cancelUrl", cancelUrl);
|
|
180
|
+
return `${baseUrl}?${params.toString()}`;
|
|
181
|
+
}
|
|
151
182
|
/**
|
|
152
183
|
* Create a payment link
|
|
153
184
|
*
|
|
@@ -181,8 +212,8 @@ var Settlr = class {
|
|
|
181
212
|
});
|
|
182
213
|
if (memo) params.set("memo", memo);
|
|
183
214
|
if (orderId) params.set("orderId", orderId);
|
|
184
|
-
if (successUrl) params.set("
|
|
185
|
-
if (cancelUrl) params.set("
|
|
215
|
+
if (successUrl) params.set("success", successUrl);
|
|
216
|
+
if (cancelUrl) params.set("cancel", cancelUrl);
|
|
186
217
|
if (paymentId) params.set("paymentId", paymentId);
|
|
187
218
|
const checkoutUrl = `${baseUrl}?${params.toString()}`;
|
|
188
219
|
const qrCode = await this.generateQRCode(checkoutUrl);
|
|
@@ -426,49 +457,34 @@ var Settlr = class {
|
|
|
426
457
|
|
|
427
458
|
// src/react.tsx
|
|
428
459
|
import { createContext, useContext, useMemo } from "react";
|
|
429
|
-
import { useConnection, useWallet } from "@solana/wallet-adapter-react";
|
|
430
460
|
import { jsx } from "react/jsx-runtime";
|
|
431
461
|
var SettlrContext = createContext(null);
|
|
432
|
-
function SettlrProvider({
|
|
433
|
-
|
|
434
|
-
|
|
462
|
+
function SettlrProvider({
|
|
463
|
+
children,
|
|
464
|
+
config,
|
|
465
|
+
authenticated = false
|
|
466
|
+
}) {
|
|
435
467
|
const settlr = useMemo(() => {
|
|
436
468
|
return new Settlr({
|
|
437
469
|
...config,
|
|
438
|
-
rpcEndpoint:
|
|
470
|
+
rpcEndpoint: config.rpcEndpoint ?? "https://api.devnet.solana.com"
|
|
439
471
|
});
|
|
440
|
-
}, [config
|
|
472
|
+
}, [config]);
|
|
441
473
|
const value = useMemo(
|
|
442
474
|
() => ({
|
|
443
475
|
settlr,
|
|
444
|
-
|
|
476
|
+
authenticated,
|
|
445
477
|
createPayment: (options) => {
|
|
446
478
|
return settlr.createPayment(options);
|
|
447
479
|
},
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
return {
|
|
451
|
-
success: false,
|
|
452
|
-
signature: "",
|
|
453
|
-
amount: options.amount,
|
|
454
|
-
merchantAddress: settlr.getMerchantAddress().toBase58(),
|
|
455
|
-
error: "Wallet not connected"
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
return settlr.pay({
|
|
459
|
-
wallet: {
|
|
460
|
-
publicKey: wallet.publicKey,
|
|
461
|
-
signTransaction: wallet.signTransaction
|
|
462
|
-
},
|
|
463
|
-
amount: options.amount,
|
|
464
|
-
memo: options.memo
|
|
465
|
-
});
|
|
480
|
+
getCheckoutUrl: (options) => {
|
|
481
|
+
return settlr.getCheckoutUrl(options);
|
|
466
482
|
},
|
|
467
483
|
getBalance: () => {
|
|
468
484
|
return settlr.getMerchantBalance();
|
|
469
485
|
}
|
|
470
486
|
}),
|
|
471
|
-
[settlr,
|
|
487
|
+
[settlr, authenticated]
|
|
472
488
|
);
|
|
473
489
|
return /* @__PURE__ */ jsx(SettlrContext.Provider, { value, children });
|
|
474
490
|
}
|
|
@@ -541,7 +557,8 @@ function BuyButton({
|
|
|
541
557
|
onSuccess,
|
|
542
558
|
onError,
|
|
543
559
|
onProcessing,
|
|
544
|
-
useRedirect =
|
|
560
|
+
useRedirect = true,
|
|
561
|
+
// Default to redirect flow (works with Privy)
|
|
545
562
|
successUrl,
|
|
546
563
|
cancelUrl,
|
|
547
564
|
className,
|
|
@@ -550,7 +567,7 @@ function BuyButton({
|
|
|
550
567
|
variant = "primary",
|
|
551
568
|
size = "md"
|
|
552
569
|
}) {
|
|
553
|
-
const {
|
|
570
|
+
const { getCheckoutUrl, createPayment } = useSettlr();
|
|
554
571
|
const [loading, setLoading] = useState(false);
|
|
555
572
|
const [status, setStatus] = useState("idle");
|
|
556
573
|
const handleClick = useCallback(async () => {
|
|
@@ -559,32 +576,17 @@ function BuyButton({
|
|
|
559
576
|
setStatus("processing");
|
|
560
577
|
onProcessing?.();
|
|
561
578
|
try {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
window.location.href = payment.checkoutUrl;
|
|
571
|
-
} else {
|
|
572
|
-
const result = await pay({ amount, memo });
|
|
573
|
-
if (result.success) {
|
|
574
|
-
setStatus("success");
|
|
575
|
-
onSuccess?.({
|
|
576
|
-
signature: result.signature,
|
|
577
|
-
amount: result.amount,
|
|
578
|
-
merchantAddress: result.merchantAddress
|
|
579
|
-
});
|
|
580
|
-
} else {
|
|
581
|
-
throw new Error(result.error || "Payment failed");
|
|
582
|
-
}
|
|
583
|
-
}
|
|
579
|
+
const url = getCheckoutUrl({
|
|
580
|
+
amount,
|
|
581
|
+
memo,
|
|
582
|
+
orderId,
|
|
583
|
+
successUrl,
|
|
584
|
+
cancelUrl
|
|
585
|
+
});
|
|
586
|
+
window.location.href = url;
|
|
584
587
|
} catch (error) {
|
|
585
588
|
setStatus("error");
|
|
586
589
|
onError?.(error instanceof Error ? error : new Error("Payment failed"));
|
|
587
|
-
} finally {
|
|
588
590
|
setLoading(false);
|
|
589
591
|
}
|
|
590
592
|
}, [
|
|
@@ -593,12 +595,9 @@ function BuyButton({
|
|
|
593
595
|
orderId,
|
|
594
596
|
disabled,
|
|
595
597
|
loading,
|
|
596
|
-
useRedirect,
|
|
597
598
|
successUrl,
|
|
598
599
|
cancelUrl,
|
|
599
|
-
|
|
600
|
-
createPayment,
|
|
601
|
-
onSuccess,
|
|
600
|
+
getCheckoutUrl,
|
|
602
601
|
onError,
|
|
603
602
|
onProcessing
|
|
604
603
|
]);
|
|
@@ -618,11 +617,11 @@ function BuyButton({
|
|
|
618
617
|
"button",
|
|
619
618
|
{
|
|
620
619
|
onClick: handleClick,
|
|
621
|
-
disabled: disabled || loading
|
|
620
|
+
disabled: disabled || loading,
|
|
622
621
|
className,
|
|
623
622
|
style: buttonStyle,
|
|
624
623
|
type: "button",
|
|
625
|
-
children:
|
|
624
|
+
children: buttonContent
|
|
626
625
|
}
|
|
627
626
|
);
|
|
628
627
|
}
|
|
@@ -746,7 +745,7 @@ function CheckoutWidget({
|
|
|
746
745
|
theme = "dark",
|
|
747
746
|
showBranding = true
|
|
748
747
|
}) {
|
|
749
|
-
const {
|
|
748
|
+
const { getCheckoutUrl } = useSettlr();
|
|
750
749
|
const [status, setStatus] = useState("idle");
|
|
751
750
|
const containerStyle = {
|
|
752
751
|
...widgetStyles.container,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@settlr/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Solana USDC payments
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Solana USDC payments for games - email checkout, no wallet required",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"license": "MIT",
|
|
37
37
|
"repository": {
|
|
38
38
|
"type": "git",
|
|
39
|
-
"url": "https://github.com/ABFX15/x402-hack-payment"
|
|
39
|
+
"url": "git+https://github.com/ABFX15/x402-hack-payment.git"
|
|
40
40
|
},
|
|
41
41
|
"homepage": "https://settlr.dev",
|
|
42
42
|
"dependencies": {
|
|
@@ -44,7 +44,6 @@
|
|
|
44
44
|
"@solana/web3.js": "^1.98.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@solana/wallet-adapter-react": "^0.15.39",
|
|
48
47
|
"@types/node": "^20.0.0",
|
|
49
48
|
"@types/react": "^19.2.7",
|
|
50
49
|
"react": "^19.2.3",
|
|
@@ -52,10 +51,10 @@
|
|
|
52
51
|
"typescript": "^5.0.0"
|
|
53
52
|
},
|
|
54
53
|
"peerDependencies": {
|
|
55
|
-
"
|
|
54
|
+
"react": ">=18.0.0"
|
|
56
55
|
},
|
|
57
56
|
"peerDependenciesMeta": {
|
|
58
|
-
"
|
|
57
|
+
"react": {
|
|
59
58
|
"optional": true
|
|
60
59
|
}
|
|
61
60
|
},
|