uvd-x402-sdk 2.15.2 → 2.16.2
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 +247 -6
- package/dist/adapters/index.d.mts +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +82 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/index.mjs +82 -1
- package/dist/adapters/index.mjs.map +1 -1
- package/dist/backend/index.d.mts +1 -1
- package/dist/backend/index.d.ts +1 -1
- package/dist/backend/index.js +82 -1
- package/dist/backend/index.js.map +1 -1
- package/dist/backend/index.mjs +82 -1
- package/dist/backend/index.mjs.map +1 -1
- package/dist/{index-Cwi_VM05.d.ts → index-B2cQzyKa.d.ts} +10 -2
- package/dist/{index-BYIugZlE.d.mts → index-BE5cH7oS.d.mts} +48 -5
- package/dist/{index-BYIugZlE.d.ts → index-BE5cH7oS.d.ts} +48 -5
- package/dist/{index-D3PO3jLW.d.mts → index-oE4dj05k.d.mts} +10 -2
- package/dist/index.d.mts +11 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.js +111 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +110 -3
- package/dist/index.mjs.map +1 -1
- package/dist/providers/algorand/index.d.mts +1 -1
- package/dist/providers/algorand/index.d.ts +1 -1
- package/dist/providers/algorand/index.js +95 -2
- package/dist/providers/algorand/index.js.map +1 -1
- package/dist/providers/algorand/index.mjs +95 -2
- package/dist/providers/algorand/index.mjs.map +1 -1
- package/dist/providers/evm/index.d.mts +1 -1
- package/dist/providers/evm/index.d.ts +1 -1
- package/dist/providers/evm/index.js +82 -1
- package/dist/providers/evm/index.js.map +1 -1
- package/dist/providers/evm/index.mjs +82 -1
- package/dist/providers/evm/index.mjs.map +1 -1
- package/dist/providers/near/index.d.mts +1 -1
- package/dist/providers/near/index.d.ts +1 -1
- package/dist/providers/near/index.js +82 -1
- package/dist/providers/near/index.js.map +1 -1
- package/dist/providers/near/index.mjs +82 -1
- package/dist/providers/near/index.mjs.map +1 -1
- package/dist/providers/solana/index.d.mts +1 -1
- package/dist/providers/solana/index.d.ts +1 -1
- package/dist/providers/solana/index.js +82 -1
- package/dist/providers/solana/index.js.map +1 -1
- package/dist/providers/solana/index.mjs +82 -1
- package/dist/providers/solana/index.mjs.map +1 -1
- package/dist/providers/stellar/index.d.mts +1 -1
- package/dist/providers/stellar/index.d.ts +1 -1
- package/dist/providers/stellar/index.js +82 -1
- package/dist/providers/stellar/index.js.map +1 -1
- package/dist/providers/stellar/index.mjs +82 -1
- package/dist/providers/stellar/index.mjs.map +1 -1
- package/dist/providers/sui/index.d.mts +105 -0
- package/dist/providers/sui/index.d.ts +105 -0
- package/dist/providers/sui/index.js +1027 -0
- package/dist/providers/sui/index.js.map +1 -0
- package/dist/providers/sui/index.mjs +1022 -0
- package/dist/providers/sui/index.mjs.map +1 -0
- package/dist/react/index.d.mts +3 -3
- package/dist/react/index.d.ts +3 -3
- package/dist/react/index.js +82 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +82 -1
- package/dist/react/index.mjs.map +1 -1
- package/dist/utils/index.d.mts +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +82 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs +82 -1
- package/dist/utils/index.mjs.map +1 -1
- package/package.json +15 -4
- package/src/chains/index.ts +98 -0
- package/src/facilitator.ts +18 -0
- package/src/index.ts +20 -1
- package/src/providers/sui/index.ts +431 -0
- package/src/types/index.ts +55 -3
package/src/facilitator.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* - Algorand: Signing fee transactions in atomic groups
|
|
12
12
|
* - Stellar: Signing authorization entries
|
|
13
13
|
* - NEAR: Acting as relayer for meta-transactions
|
|
14
|
+
* - Sui: Sponsoring transactions (paying gas via sponsored txs)
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -109,6 +110,20 @@ export const FACILITATOR_ADDRESSES = {
|
|
|
109
110
|
* Algorand facilitator address (testnet)
|
|
110
111
|
*/
|
|
111
112
|
'algorand-testnet': '5DPPDQNYUPCTXRZWRYSF3WPYU6RKAUR25F3YG4EKXQRHV5AUAI62H5GXL4',
|
|
113
|
+
|
|
114
|
+
// ============================================
|
|
115
|
+
// Sui
|
|
116
|
+
// ============================================
|
|
117
|
+
/**
|
|
118
|
+
* Sui facilitator address (mainnet)
|
|
119
|
+
* Used for: Sponsoring transactions (paying gas)
|
|
120
|
+
*/
|
|
121
|
+
sui: '0xe7bbf2b13f7d72714760aa16e024fa1b35a978793f9893d0568a4fbf356a764a',
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Sui facilitator address (testnet)
|
|
125
|
+
*/
|
|
126
|
+
'sui-testnet': '0xabbd16a2fab2a502c9cfe835195a6fc7d70bfc27cffb40b8b286b52a97006e67',
|
|
112
127
|
} as const;
|
|
113
128
|
|
|
114
129
|
/**
|
|
@@ -144,6 +159,9 @@ export function getFacilitatorAddress(
|
|
|
144
159
|
if (networkType === 'near') {
|
|
145
160
|
return FACILITATOR_ADDRESSES.near;
|
|
146
161
|
}
|
|
162
|
+
if (networkType === 'sui') {
|
|
163
|
+
return FACILITATOR_ADDRESSES.sui;
|
|
164
|
+
}
|
|
147
165
|
|
|
148
166
|
return undefined;
|
|
149
167
|
}
|
package/src/index.ts
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* x402 Payment SDK - Gasless crypto payments using the Ultravioleta facilitator.
|
|
5
5
|
*
|
|
6
|
-
* Supports
|
|
6
|
+
* Supports 18 blockchain networks:
|
|
7
7
|
* - EVM (10): Base, Ethereum, Polygon, Arbitrum, Optimism, Avalanche, Celo, HyperEVM, Unichain, Monad
|
|
8
8
|
* - SVM (2): Solana, Fogo
|
|
9
9
|
* - Stellar (1): Stellar
|
|
10
10
|
* - NEAR (1): NEAR Protocol
|
|
11
11
|
* - Algorand (2): Algorand mainnet and testnet
|
|
12
|
+
* - Sui (2): Sui mainnet and testnet
|
|
12
13
|
*
|
|
13
14
|
* Supports both x402 v1 and v2 protocols.
|
|
14
15
|
*
|
|
@@ -73,6 +74,19 @@
|
|
|
73
74
|
* const header = algorand.encodePaymentHeader(payload, algorandConfig);
|
|
74
75
|
* ```
|
|
75
76
|
*
|
|
77
|
+
* @example With Sui (Sponsored Transactions)
|
|
78
|
+
* ```ts
|
|
79
|
+
* import { SuiProvider } from 'uvd-x402-sdk/sui';
|
|
80
|
+
* import { getChainByName } from 'uvd-x402-sdk';
|
|
81
|
+
*
|
|
82
|
+
* const sui = new SuiProvider();
|
|
83
|
+
* const address = await sui.connect();
|
|
84
|
+
* const suiConfig = getChainByName('sui')!;
|
|
85
|
+
* const payload = await sui.signPayment(paymentInfo, suiConfig);
|
|
86
|
+
* const header = sui.encodePaymentHeader(payload, suiConfig);
|
|
87
|
+
* // User pays ZERO gas - facilitator sponsors the transaction
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
76
90
|
* @example With React
|
|
77
91
|
* ```tsx
|
|
78
92
|
* import { X402Provider, useX402, usePayment } from 'uvd-x402-sdk/react';
|
|
@@ -116,6 +130,9 @@ export {
|
|
|
116
130
|
// Algorand helper functions
|
|
117
131
|
getAlgorandChains,
|
|
118
132
|
isAlgorandChain,
|
|
133
|
+
// Sui helper functions
|
|
134
|
+
getSuiChains,
|
|
135
|
+
isSuiChain,
|
|
119
136
|
} from './chains';
|
|
120
137
|
|
|
121
138
|
// x402 utilities
|
|
@@ -171,6 +188,7 @@ export type {
|
|
|
171
188
|
StellarPaymentPayload,
|
|
172
189
|
NEARPaymentPayload,
|
|
173
190
|
AlgorandPaymentPayload,
|
|
191
|
+
SuiPaymentPayload,
|
|
174
192
|
X402HeaderName,
|
|
175
193
|
|
|
176
194
|
// x402 header types (v1 and v2)
|
|
@@ -185,6 +203,7 @@ export type {
|
|
|
185
203
|
X402StellarPayload,
|
|
186
204
|
X402NEARPayload,
|
|
187
205
|
X402AlgorandPayload,
|
|
206
|
+
X402SuiPayload,
|
|
188
207
|
|
|
189
208
|
// Config types
|
|
190
209
|
X402ClientConfig,
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* uvd-x402-sdk - Sui Provider
|
|
3
|
+
*
|
|
4
|
+
* Provides wallet connection and payment creation for Sui blockchain.
|
|
5
|
+
* Uses sponsored transactions where the facilitator pays gas fees.
|
|
6
|
+
*
|
|
7
|
+
* @example Sui Mainnet
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { SuiProvider } from 'uvd-x402-sdk/sui';
|
|
10
|
+
* import { getChainByName } from 'uvd-x402-sdk';
|
|
11
|
+
*
|
|
12
|
+
* const sui = new SuiProvider();
|
|
13
|
+
*
|
|
14
|
+
* // Connect
|
|
15
|
+
* const address = await sui.connect();
|
|
16
|
+
*
|
|
17
|
+
* // Create payment
|
|
18
|
+
* const chainConfig = getChainByName('sui')!;
|
|
19
|
+
* const paymentPayload = await sui.signPayment(paymentInfo, chainConfig);
|
|
20
|
+
* const header = sui.encodePaymentHeader(paymentPayload, chainConfig);
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @example Sui Testnet
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { SuiProvider } from 'uvd-x402-sdk/sui';
|
|
26
|
+
* import { getChainByName } from 'uvd-x402-sdk';
|
|
27
|
+
*
|
|
28
|
+
* const sui = new SuiProvider();
|
|
29
|
+
*
|
|
30
|
+
* // Connect
|
|
31
|
+
* const address = await sui.connect();
|
|
32
|
+
*
|
|
33
|
+
* // Create testnet payment
|
|
34
|
+
* const chainConfig = getChainByName('sui-testnet')!;
|
|
35
|
+
* const paymentPayload = await sui.signPayment(paymentInfo, chainConfig);
|
|
36
|
+
* const header = sui.encodePaymentHeader(paymentPayload, chainConfig);
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import type {
|
|
41
|
+
ChainConfig,
|
|
42
|
+
PaymentInfo,
|
|
43
|
+
SuiPaymentPayload,
|
|
44
|
+
WalletAdapter,
|
|
45
|
+
X402Version,
|
|
46
|
+
} from '../../types';
|
|
47
|
+
import { X402Error } from '../../types';
|
|
48
|
+
import { getChainByName } from '../../chains';
|
|
49
|
+
import { chainToCAIP2 } from '../../utils';
|
|
50
|
+
|
|
51
|
+
// Lazy import Sui dependencies to avoid bundling when not used
|
|
52
|
+
let SuiClient: typeof import('@mysten/sui/client').SuiClient;
|
|
53
|
+
let Transaction: typeof import('@mysten/sui/transactions').Transaction;
|
|
54
|
+
|
|
55
|
+
async function loadSuiDeps() {
|
|
56
|
+
if (!SuiClient) {
|
|
57
|
+
const clientModule = await import('@mysten/sui/client');
|
|
58
|
+
const txModule = await import('@mysten/sui/transactions');
|
|
59
|
+
SuiClient = clientModule.SuiClient;
|
|
60
|
+
Transaction = txModule.Transaction;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Sui wallet provider interface (for Sui Wallet, Suiet, etc.)
|
|
66
|
+
*/
|
|
67
|
+
interface SuiWalletProvider {
|
|
68
|
+
hasPermissions?: () => Promise<boolean>;
|
|
69
|
+
requestPermissions?: () => Promise<boolean>;
|
|
70
|
+
getAccounts: () => Promise<string[]>;
|
|
71
|
+
signTransactionBlock?: (input: {
|
|
72
|
+
transactionBlock: Uint8Array;
|
|
73
|
+
account: string;
|
|
74
|
+
chain: string;
|
|
75
|
+
}) => Promise<{
|
|
76
|
+
signature: string;
|
|
77
|
+
transactionBlockBytes: string;
|
|
78
|
+
}>;
|
|
79
|
+
signTransaction?: (input: {
|
|
80
|
+
transaction: Uint8Array;
|
|
81
|
+
account: string;
|
|
82
|
+
chain: string;
|
|
83
|
+
}) => Promise<{
|
|
84
|
+
signature: string;
|
|
85
|
+
bytes: string;
|
|
86
|
+
}>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* SuiProvider - Wallet adapter for Sui blockchain
|
|
91
|
+
*
|
|
92
|
+
* Uses sponsored transactions where:
|
|
93
|
+
* 1. User creates a programmable transaction for token transfer
|
|
94
|
+
* 2. User signs the transaction
|
|
95
|
+
* 3. Facilitator sponsors (pays gas) and submits
|
|
96
|
+
*/
|
|
97
|
+
export class SuiProvider implements WalletAdapter {
|
|
98
|
+
readonly id = 'sui-wallet';
|
|
99
|
+
readonly name = 'Sui Wallet';
|
|
100
|
+
readonly networkType = 'sui' as const;
|
|
101
|
+
|
|
102
|
+
private provider: SuiWalletProvider | null = null;
|
|
103
|
+
private clients: Map<string, InstanceType<typeof SuiClient>> = new Map();
|
|
104
|
+
private address: string | null = null;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check if Sui wallet is available
|
|
108
|
+
*/
|
|
109
|
+
isAvailable(): boolean {
|
|
110
|
+
if (typeof window === 'undefined') return false;
|
|
111
|
+
const win = window as Window & { suiWallet?: SuiWalletProvider };
|
|
112
|
+
return !!win.suiWallet;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Connect to Sui wallet
|
|
117
|
+
*/
|
|
118
|
+
async connect(): Promise<string> {
|
|
119
|
+
await loadSuiDeps();
|
|
120
|
+
|
|
121
|
+
// Get Sui wallet provider
|
|
122
|
+
this.provider = await this.getSuiWalletProvider();
|
|
123
|
+
if (!this.provider) {
|
|
124
|
+
throw new X402Error(
|
|
125
|
+
'Sui wallet not installed. Please install Sui Wallet or Suiet from Chrome Web Store',
|
|
126
|
+
'WALLET_NOT_FOUND'
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
// Request permissions if needed
|
|
132
|
+
if (this.provider.requestPermissions) {
|
|
133
|
+
await this.provider.requestPermissions();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Get accounts
|
|
137
|
+
const accounts = await this.provider.getAccounts();
|
|
138
|
+
if (!accounts || accounts.length === 0) {
|
|
139
|
+
throw new X402Error('No Sui accounts found', 'WALLET_CONNECTION_FAILED');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
this.address = accounts[0];
|
|
143
|
+
return this.address;
|
|
144
|
+
} catch (error: unknown) {
|
|
145
|
+
if (error instanceof X402Error) throw error;
|
|
146
|
+
if (error instanceof Error) {
|
|
147
|
+
if (error.message.includes('User rejected') || error.message.includes('rejected')) {
|
|
148
|
+
throw new X402Error('Connection rejected by user', 'WALLET_CONNECTION_REJECTED');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
throw new X402Error(
|
|
152
|
+
`Failed to connect Sui wallet: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
153
|
+
'UNKNOWN_ERROR',
|
|
154
|
+
error
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Disconnect from Sui wallet
|
|
161
|
+
*/
|
|
162
|
+
async disconnect(): Promise<void> {
|
|
163
|
+
this.provider = null;
|
|
164
|
+
this.clients.clear();
|
|
165
|
+
this.address = null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get current address
|
|
170
|
+
*/
|
|
171
|
+
getAddress(): string | null {
|
|
172
|
+
return this.address;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get USDC balance
|
|
177
|
+
*/
|
|
178
|
+
async getBalance(chainConfig: ChainConfig): Promise<string> {
|
|
179
|
+
await loadSuiDeps();
|
|
180
|
+
|
|
181
|
+
if (!this.address) {
|
|
182
|
+
throw new X402Error('Wallet not connected', 'WALLET_NOT_CONNECTED');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const client = await this.getClient(chainConfig);
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
// Get all USDC coins for this address
|
|
189
|
+
const coins = await client.getCoins({
|
|
190
|
+
owner: this.address,
|
|
191
|
+
coinType: chainConfig.usdc.address,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
if (!coins.data || coins.data.length === 0) {
|
|
195
|
+
return '0.00';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Sum all USDC balances
|
|
199
|
+
const totalBalance = coins.data.reduce((sum, coin) => {
|
|
200
|
+
return sum + BigInt(coin.balance);
|
|
201
|
+
}, BigInt(0));
|
|
202
|
+
|
|
203
|
+
const balance = Number(totalBalance) / Math.pow(10, chainConfig.usdc.decimals);
|
|
204
|
+
return balance.toFixed(2);
|
|
205
|
+
} catch {
|
|
206
|
+
return '0.00';
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Create Sui payment (sponsored transaction)
|
|
212
|
+
*
|
|
213
|
+
* Transaction structure:
|
|
214
|
+
* 1. User creates a programmable transaction for USDC transfer
|
|
215
|
+
* 2. Transaction is signed by the user
|
|
216
|
+
* 3. Facilitator sponsors the transaction (pays gas in SUI)
|
|
217
|
+
* 4. Facilitator adds sponsor signature and submits
|
|
218
|
+
*
|
|
219
|
+
* User pays: ZERO SUI
|
|
220
|
+
*/
|
|
221
|
+
async signPayment(paymentInfo: PaymentInfo, chainConfig: ChainConfig): Promise<string> {
|
|
222
|
+
await loadSuiDeps();
|
|
223
|
+
|
|
224
|
+
if (!this.provider || !this.address) {
|
|
225
|
+
throw new X402Error('Wallet not connected', 'WALLET_NOT_CONNECTED');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const client = await this.getClient(chainConfig);
|
|
229
|
+
|
|
230
|
+
// Get recipient address
|
|
231
|
+
const recipient = paymentInfo.recipients?.sui || paymentInfo.recipient;
|
|
232
|
+
const facilitatorAddress = paymentInfo.facilitator;
|
|
233
|
+
|
|
234
|
+
if (!facilitatorAddress) {
|
|
235
|
+
throw new X402Error('Facilitator address not provided', 'INVALID_CONFIG');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Parse amount (6 decimals for USDC)
|
|
239
|
+
const amount = BigInt(Math.floor(parseFloat(paymentInfo.amount) * 1_000_000));
|
|
240
|
+
|
|
241
|
+
// Find USDC coins for transfer
|
|
242
|
+
const usdcCoins = await client.getCoins({
|
|
243
|
+
owner: this.address,
|
|
244
|
+
coinType: chainConfig.usdc.address,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
if (!usdcCoins.data || usdcCoins.data.length === 0) {
|
|
248
|
+
throw new X402Error('No USDC coins found in wallet', 'INSUFFICIENT_BALANCE');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Check total balance
|
|
252
|
+
const totalBalance = usdcCoins.data.reduce((sum, coin) => sum + BigInt(coin.balance), BigInt(0));
|
|
253
|
+
if (totalBalance < amount) {
|
|
254
|
+
throw new X402Error(
|
|
255
|
+
`Insufficient USDC balance. Have: ${Number(totalBalance) / 1_000_000}, Need: ${Number(amount) / 1_000_000}`,
|
|
256
|
+
'INSUFFICIENT_BALANCE'
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Build the programmable transaction
|
|
261
|
+
const tx = new Transaction();
|
|
262
|
+
|
|
263
|
+
// Set the gas sponsor (facilitator pays)
|
|
264
|
+
tx.setSender(this.address);
|
|
265
|
+
tx.setGasOwner(facilitatorAddress);
|
|
266
|
+
|
|
267
|
+
// Find a coin with enough balance or merge coins
|
|
268
|
+
let coinToUse: string;
|
|
269
|
+
const sufficientCoin = usdcCoins.data.find(c => BigInt(c.balance) >= amount);
|
|
270
|
+
|
|
271
|
+
if (sufficientCoin) {
|
|
272
|
+
coinToUse = sufficientCoin.coinObjectId;
|
|
273
|
+
} else {
|
|
274
|
+
// Need to merge coins - use the first coin as base
|
|
275
|
+
const baseCoin = usdcCoins.data[0];
|
|
276
|
+
coinToUse = baseCoin.coinObjectId;
|
|
277
|
+
|
|
278
|
+
// Merge other coins into the first one
|
|
279
|
+
const otherCoins = usdcCoins.data.slice(1).map(c => c.coinObjectId);
|
|
280
|
+
if (otherCoins.length > 0) {
|
|
281
|
+
tx.mergeCoins(tx.object(coinToUse), otherCoins.map(id => tx.object(id)));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Split the exact amount and transfer to recipient
|
|
286
|
+
const [paymentCoin] = tx.splitCoins(tx.object(coinToUse), [amount]);
|
|
287
|
+
tx.transferObjects([paymentCoin], recipient);
|
|
288
|
+
|
|
289
|
+
// Build transaction bytes
|
|
290
|
+
const txBytes = await tx.build({ client });
|
|
291
|
+
|
|
292
|
+
// Sign the transaction
|
|
293
|
+
let signedTx: { signature: string; bytes: string };
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
// Try new API first (signTransaction)
|
|
297
|
+
if (this.provider.signTransaction) {
|
|
298
|
+
signedTx = await this.provider.signTransaction({
|
|
299
|
+
transaction: txBytes,
|
|
300
|
+
account: this.address,
|
|
301
|
+
chain: chainConfig.name === 'sui-testnet' ? 'sui:testnet' : 'sui:mainnet',
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
// Fall back to old API (signTransactionBlock)
|
|
305
|
+
else if (this.provider.signTransactionBlock) {
|
|
306
|
+
const result = await this.provider.signTransactionBlock({
|
|
307
|
+
transactionBlock: txBytes,
|
|
308
|
+
account: this.address,
|
|
309
|
+
chain: chainConfig.name === 'sui-testnet' ? 'sui:testnet' : 'sui:mainnet',
|
|
310
|
+
});
|
|
311
|
+
signedTx = {
|
|
312
|
+
signature: result.signature,
|
|
313
|
+
bytes: result.transactionBlockBytes,
|
|
314
|
+
};
|
|
315
|
+
} else {
|
|
316
|
+
throw new X402Error('Wallet does not support transaction signing', 'WALLET_NOT_SUPPORTED');
|
|
317
|
+
}
|
|
318
|
+
} catch (error: unknown) {
|
|
319
|
+
if (error instanceof X402Error) throw error;
|
|
320
|
+
if (error instanceof Error) {
|
|
321
|
+
if (error.message.includes('User rejected') || error.message.includes('rejected')) {
|
|
322
|
+
throw new X402Error('Signature rejected by user', 'SIGNATURE_REJECTED');
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
throw new X402Error(
|
|
326
|
+
`Failed to sign transaction: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
327
|
+
'PAYMENT_FAILED',
|
|
328
|
+
error
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Build the payload for the facilitator
|
|
333
|
+
// CRITICAL: coinObjectId is REQUIRED by the facilitator for deserialization
|
|
334
|
+
const payload: SuiPaymentPayload = {
|
|
335
|
+
transactionBytes: signedTx.bytes,
|
|
336
|
+
senderSignature: signedTx.signature,
|
|
337
|
+
from: this.address,
|
|
338
|
+
to: recipient,
|
|
339
|
+
amount: amount.toString(),
|
|
340
|
+
coinObjectId: coinToUse,
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
return JSON.stringify(payload);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Encode Sui payment as X-PAYMENT header
|
|
348
|
+
*
|
|
349
|
+
* @param paymentPayload - JSON-encoded payment payload from signPayment()
|
|
350
|
+
* @param chainConfig - Chain configuration (optional, defaults to 'sui')
|
|
351
|
+
* @param version - x402 protocol version (1 or 2, defaults to 1)
|
|
352
|
+
* @returns Base64-encoded X-PAYMENT header value
|
|
353
|
+
*/
|
|
354
|
+
encodePaymentHeader(
|
|
355
|
+
paymentPayload: string,
|
|
356
|
+
chainConfig?: ChainConfig,
|
|
357
|
+
version: X402Version = 1
|
|
358
|
+
): string {
|
|
359
|
+
const payload = JSON.parse(paymentPayload) as SuiPaymentPayload;
|
|
360
|
+
|
|
361
|
+
// Use chain name from config, or default to 'sui' for backward compatibility
|
|
362
|
+
const networkName = chainConfig?.name || 'sui';
|
|
363
|
+
|
|
364
|
+
// Format in x402 standard format (v1 or v2)
|
|
365
|
+
const x402Payload = version === 2
|
|
366
|
+
? {
|
|
367
|
+
x402Version: 2 as const,
|
|
368
|
+
scheme: 'exact' as const,
|
|
369
|
+
network: chainToCAIP2(networkName), // CAIP-2 format for v2
|
|
370
|
+
payload: payload,
|
|
371
|
+
}
|
|
372
|
+
: {
|
|
373
|
+
x402Version: 1 as const,
|
|
374
|
+
scheme: 'exact' as const,
|
|
375
|
+
network: networkName, // Plain chain name for v1
|
|
376
|
+
payload: payload,
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
return btoa(JSON.stringify(x402Payload));
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Private helpers
|
|
383
|
+
|
|
384
|
+
private async getSuiWalletProvider(): Promise<SuiWalletProvider | null> {
|
|
385
|
+
if (typeof window === 'undefined') return null;
|
|
386
|
+
|
|
387
|
+
const win = window as Window & { suiWallet?: SuiWalletProvider };
|
|
388
|
+
|
|
389
|
+
// Check for Sui Wallet
|
|
390
|
+
if (win.suiWallet) {
|
|
391
|
+
return win.suiWallet;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Wait a bit for wallet to inject itself
|
|
395
|
+
for (let i = 0; i < 5; i++) {
|
|
396
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
397
|
+
if (win.suiWallet) {
|
|
398
|
+
return win.suiWallet;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Get or create a Sui client for a specific chain
|
|
407
|
+
*/
|
|
408
|
+
private async getClient(chainConfig?: ChainConfig): Promise<InstanceType<typeof SuiClient>> {
|
|
409
|
+
await loadSuiDeps();
|
|
410
|
+
|
|
411
|
+
const config = chainConfig || getChainByName('sui');
|
|
412
|
+
if (!config) {
|
|
413
|
+
throw new X402Error('Chain config not found', 'CHAIN_NOT_SUPPORTED');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Cache by RPC URL
|
|
417
|
+
const cacheKey = config.rpcUrl;
|
|
418
|
+
|
|
419
|
+
if (this.clients.has(cacheKey)) {
|
|
420
|
+
return this.clients.get(cacheKey)!;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const client = new SuiClient({ url: config.rpcUrl });
|
|
424
|
+
this.clients.set(cacheKey, client);
|
|
425
|
+
|
|
426
|
+
return client;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Default export
|
|
431
|
+
export default SuiProvider;
|
package/src/types/index.ts
CHANGED
|
@@ -16,10 +16,11 @@
|
|
|
16
16
|
* - 'stellar': Stellar network (use Soroban)
|
|
17
17
|
* - 'near': NEAR Protocol (use NEP-366)
|
|
18
18
|
* - 'algorand': Algorand network (use ASA transfers with atomic transactions)
|
|
19
|
+
* - 'sui': Sui blockchain (use sponsored transactions)
|
|
19
20
|
*
|
|
20
21
|
* @deprecated 'solana' type is deprecated, use 'svm' instead
|
|
21
22
|
*/
|
|
22
|
-
export type NetworkType = 'evm' | 'svm' | 'solana' | 'stellar' | 'near' | 'algorand';
|
|
23
|
+
export type NetworkType = 'evm' | 'svm' | 'solana' | 'stellar' | 'near' | 'algorand' | 'sui';
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Supported stablecoin token types
|
|
@@ -202,6 +203,7 @@ export interface PaymentInfo {
|
|
|
202
203
|
near?: string;
|
|
203
204
|
stellar?: string;
|
|
204
205
|
algorand?: string;
|
|
206
|
+
sui?: string;
|
|
205
207
|
};
|
|
206
208
|
/** Facilitator address (for Solana fee payer) */
|
|
207
209
|
facilitator?: string;
|
|
@@ -380,6 +382,31 @@ export interface AlgorandPaymentPayload {
|
|
|
380
382
|
paymentGroup: string[];
|
|
381
383
|
}
|
|
382
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Sui payment payload (sponsored transaction)
|
|
387
|
+
*
|
|
388
|
+
* Uses Sui sponsored transactions where:
|
|
389
|
+
* - User creates a programmable transaction for USDC transfer
|
|
390
|
+
* - User signs the transaction
|
|
391
|
+
* - Facilitator sponsors (pays gas in SUI) and submits
|
|
392
|
+
*
|
|
393
|
+
* User pays: ZERO SUI
|
|
394
|
+
*/
|
|
395
|
+
export interface SuiPaymentPayload {
|
|
396
|
+
/** Base64-encoded BCS serialized TransactionData */
|
|
397
|
+
transactionBytes: string;
|
|
398
|
+
/** Base64-encoded user signature */
|
|
399
|
+
senderSignature: string;
|
|
400
|
+
/** Sender address (0x + 64 hex chars) */
|
|
401
|
+
from: string;
|
|
402
|
+
/** Recipient address (0x + 64 hex chars) */
|
|
403
|
+
to: string;
|
|
404
|
+
/** Amount in base units (string to handle large numbers) */
|
|
405
|
+
amount: string;
|
|
406
|
+
/** Coin object ID used for the transfer (REQUIRED by facilitator) */
|
|
407
|
+
coinObjectId: string;
|
|
408
|
+
}
|
|
409
|
+
|
|
383
410
|
/**
|
|
384
411
|
* Union type for all payment payloads
|
|
385
412
|
*/
|
|
@@ -388,7 +415,8 @@ export type PaymentPayload =
|
|
|
388
415
|
| SolanaPaymentPayload
|
|
389
416
|
| StellarPaymentPayload
|
|
390
417
|
| NEARPaymentPayload
|
|
391
|
-
| AlgorandPaymentPayload
|
|
418
|
+
| AlgorandPaymentPayload
|
|
419
|
+
| SuiPaymentPayload;
|
|
392
420
|
|
|
393
421
|
// ============================================================================
|
|
394
422
|
// X402 HEADER TYPES (v1 and v2)
|
|
@@ -425,6 +453,9 @@ export const CAIP2_IDENTIFIERS: Record<string, string> = {
|
|
|
425
453
|
// Algorand
|
|
426
454
|
algorand: 'algorand:mainnet',
|
|
427
455
|
'algorand-testnet': 'algorand:testnet',
|
|
456
|
+
// Sui
|
|
457
|
+
sui: 'sui:mainnet',
|
|
458
|
+
'sui-testnet': 'sui:testnet',
|
|
428
459
|
};
|
|
429
460
|
|
|
430
461
|
/**
|
|
@@ -522,6 +553,24 @@ export interface X402AlgorandPayload {
|
|
|
522
553
|
paymentGroup: string[];
|
|
523
554
|
}
|
|
524
555
|
|
|
556
|
+
/**
|
|
557
|
+
* Sui-specific payload in x402 header (sponsored transaction)
|
|
558
|
+
*/
|
|
559
|
+
export interface X402SuiPayload {
|
|
560
|
+
/** BCS-encoded transaction bytes (base64) */
|
|
561
|
+
transactionBytes: string;
|
|
562
|
+
/** User's signature on the transaction (base64) */
|
|
563
|
+
senderSignature: string;
|
|
564
|
+
/** Sender's Sui address (0x...) */
|
|
565
|
+
from: string;
|
|
566
|
+
/** Recipient's Sui address (0x...) */
|
|
567
|
+
to: string;
|
|
568
|
+
/** Amount in smallest unit (string to avoid precision issues) */
|
|
569
|
+
amount: string;
|
|
570
|
+
/** Coin object ID used for the transfer (REQUIRED by facilitator) */
|
|
571
|
+
coinObjectId: string;
|
|
572
|
+
}
|
|
573
|
+
|
|
525
574
|
/**
|
|
526
575
|
* Union of all x402 payload types
|
|
527
576
|
*/
|
|
@@ -530,7 +579,8 @@ export type X402PayloadData =
|
|
|
530
579
|
| X402SolanaPayload
|
|
531
580
|
| X402StellarPayload
|
|
532
581
|
| X402NEARPayload
|
|
533
|
-
| X402AlgorandPayload
|
|
582
|
+
| X402AlgorandPayload
|
|
583
|
+
| X402SuiPayload;
|
|
534
584
|
|
|
535
585
|
// ============================================================================
|
|
536
586
|
// CLIENT CONFIGURATION
|
|
@@ -656,6 +706,8 @@ export type X402EventHandler<E extends X402Event> = (data: X402EventData[E]) =>
|
|
|
656
706
|
export type X402ErrorCode =
|
|
657
707
|
| 'WALLET_NOT_FOUND'
|
|
658
708
|
| 'WALLET_NOT_CONNECTED'
|
|
709
|
+
| 'WALLET_NOT_SUPPORTED'
|
|
710
|
+
| 'WALLET_CONNECTION_FAILED'
|
|
659
711
|
| 'WALLET_CONNECTION_REJECTED'
|
|
660
712
|
| 'WALLET_CONNECTION_TIMEOUT'
|
|
661
713
|
| 'CHAIN_NOT_SUPPORTED'
|