@suimpp/mpp 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 suimpp
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,210 @@
1
+ # @suimpp/mpp
2
+
3
+ Sui USDC payment method for the [Machine Payments Protocol (MPP)](https://mpp.dev). Accept and make payments on any API — the first MPP implementation on Sui.
4
+
5
+ [![npm](https://img.shields.io/npm/v/@suimpp/mpp)](https://www.npmjs.com/package/@suimpp/mpp)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ **[Website](https://suimpp.dev)** · **[GitHub](https://github.com/mission69b/suimpp)** · **[SDK](https://www.npmjs.com/package/@t2000/sdk)** · **[CLI](https://www.npmjs.com/package/@t2000/cli)**
9
+
10
+ > **Migrated from `@mppsui/mpp`.** If you were using the old package, switch your imports to `@suimpp/mpp`.
11
+
12
+ ## What is MPP?
13
+
14
+ The [Machine Payments Protocol](https://mpp.dev) is an open standard by Stripe and Tempo Labs for agent-to-service payments. When a server returns HTTP `402 Payment Required`, the client pays automatically and retries — no API keys, no subscriptions, no human approval.
15
+
16
+ `@suimpp/mpp` adds **Sui USDC** as a payment method. It works with any MPP-compatible client or server via the `mppx` SDK.
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @suimpp/mpp mppx
22
+ ```
23
+
24
+ ## Accept Payments (Server)
25
+
26
+ Add payments to any API in 5 lines:
27
+
28
+ ```typescript
29
+ import { sui } from '@suimpp/mpp/server';
30
+ import { Mppx } from 'mppx';
31
+
32
+ const SUI_USDC = '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC';
33
+
34
+ const mppx = Mppx.create({
35
+ methods: [sui({ currency: SUI_USDC, recipient: '0xYOUR_ADDRESS' })],
36
+ });
37
+
38
+ export const GET = mppx.charge({ amount: '0.01' })(
39
+ () => Response.json({ data: 'paid content' })
40
+ );
41
+ ```
42
+
43
+ No webhooks. No Stripe dashboard. No KYC. USDC arrives directly in your wallet.
44
+
45
+ ## Make Payments (Client)
46
+
47
+ ```typescript
48
+ import { sui } from '@suimpp/mpp/client';
49
+ import { Mppx } from 'mppx/client';
50
+ import { SuiGrpcClient } from '@mysten/sui/grpc';
51
+ import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
52
+
53
+ const client = new SuiGrpcClient({
54
+ baseUrl: 'https://fullnode.mainnet.sui.io:443',
55
+ network: 'mainnet',
56
+ });
57
+ const signer = Ed25519Keypair.deriveKeypair('your mnemonic');
58
+
59
+ const mppx = Mppx.create({
60
+ methods: [sui({ client, signer })],
61
+ });
62
+
63
+ const response = await mppx.fetch('https://api.example.com/resource');
64
+ // If the API returns 402, mppx pays automatically via Sui USDC.
65
+ ```
66
+
67
+ ## With t2000 SDK
68
+
69
+ If you're using the [t2000 SDK](https://www.npmjs.com/package/@t2000/sdk), payments are even simpler:
70
+
71
+ ```typescript
72
+ import { T2000 } from '@t2000/sdk';
73
+
74
+ const agent = await T2000.create({ pin: 'my-secret' });
75
+
76
+ const result = await agent.pay({
77
+ url: 'https://api.example.com/generate',
78
+ body: { prompt: 'a sunset' },
79
+ maxPrice: 0.05,
80
+ });
81
+ // Handles 402 → pay → retry automatically.
82
+ // Safeguards enforced (max per tx, daily limits).
83
+ ```
84
+
85
+ ### CLI
86
+
87
+ ```bash
88
+ t2000 pay https://api.example.com/data --max-price 0.10
89
+
90
+ t2000 pay https://api.example.com/analyze \
91
+ --method POST \
92
+ --data '{"text":"hello"}' \
93
+ --max-price 0.05
94
+ ```
95
+
96
+ ## How It Works
97
+
98
+ ```
99
+ Agent API Server
100
+ │ │
101
+ │── GET /resource ────────>│
102
+ │<── 402 Payment Required ─│
103
+ │ {amount, currency, │
104
+ │ recipient} │
105
+ │ │
106
+ │── USDC transfer on Sui ──│ (~400ms finality)
107
+ │ │
108
+ │── GET /resource ────────>│
109
+ │ + payment credential │── verify TX on-chain via gRPC
110
+ │ (Sui tx digest) │
111
+ │<── 200 OK + data ────────│
112
+ ```
113
+
114
+ No facilitator. No intermediary. The server verifies the Sui transaction directly via gRPC.
115
+
116
+ ## Server API
117
+
118
+ ### `sui(options)`
119
+
120
+ Creates a Sui payment method for the server.
121
+
122
+ ```typescript
123
+ import { sui } from '@suimpp/mpp/server';
124
+
125
+ const method = sui({
126
+ currency: SUI_USDC, // Sui coin type (e.g. USDC)
127
+ recipient: '0xYOUR_ADDR', // Where payments are sent
128
+ decimals: 6, // Optional: currency decimals (default: 6)
129
+ rpcUrl: '...', // Optional: custom gRPC endpoint
130
+ network: 'mainnet', // Optional: 'mainnet' | 'testnet' | 'devnet'
131
+ registryUrl: 'https://suimpp.dev/api/report', // Optional: report payments to suimpp.dev
132
+ });
133
+ ```
134
+
135
+ Verification checks:
136
+ - Transaction succeeded on-chain
137
+ - Payment sent to correct recipient (address-normalized comparison)
138
+ - Amount >= requested (BigInt precision, no floating-point)
139
+
140
+ ## Client API
141
+
142
+ ### `sui(options)`
143
+
144
+ Creates a Sui payment method for the client.
145
+
146
+ ```typescript
147
+ import { sui } from '@suimpp/mpp/client';
148
+
149
+ const method = sui({
150
+ client: grpcClient, // Any Sui client (SuiGrpcClient, etc.)
151
+ signer: ed25519Keypair, // Signer from @mysten/sui/cryptography
152
+ decimals: 6, // Optional: currency decimals (default: 6)
153
+ execute: async (tx) => { // Optional: custom execution (gas sponsor, etc.)
154
+ return myGasManager.execute(tx);
155
+ },
156
+ });
157
+ ```
158
+
159
+ | Option | Type | Required | Description |
160
+ |--------|------|----------|-------------|
161
+ | `client` | `ClientWithCoreApi` | Yes | Any Sui client implementing the core API |
162
+ | `signer` | `Signer` | Yes | Any `Signer` from `@mysten/sui/cryptography` — `Ed25519Keypair` works |
163
+ | `decimals` | `number` | No | Decimal places for the currency (default: 6) |
164
+ | `execute` | `(tx: Transaction) => Promise<{ digest: string }>` | No | Override transaction execution (e.g. gas sponsor/manager) |
165
+
166
+ The client uses the [`coinWithBalance`](https://sdk.mystenlabs.com/sui/transaction-building/intents) intent to automatically resolve, merge, and split coins for the exact payment amount, then signs and broadcasts the transaction (or delegates to `execute` if provided).
167
+
168
+ ## Constants
169
+
170
+ ### `SUI_USDC_TYPE`
171
+
172
+ The Sui coin type for Circle-issued USDC on mainnet.
173
+
174
+ ```typescript
175
+ import { SUI_USDC_TYPE } from '@suimpp/mpp';
176
+ // '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC'
177
+ ```
178
+
179
+ ## Utilities
180
+
181
+ ### `parseAmountToRaw(amount, decimals)`
182
+
183
+ Converts a string amount to BigInt raw units without floating-point math.
184
+
185
+ ```typescript
186
+ parseAmountToRaw('0.01', 6); // 10000n
187
+ parseAmountToRaw('1.50', 6); // 1500000n
188
+ ```
189
+
190
+ ## Why Sui?
191
+
192
+ MPP is chain-agnostic. We chose Sui because agent payments need:
193
+
194
+ | | Sui |
195
+ |---|---|
196
+ | **Finality** | ~400ms |
197
+ | **Gas** | <$0.001 per payment |
198
+ | **USDC** | Circle-issued, native |
199
+ | **Verification** | Direct gRPC — no facilitator |
200
+
201
+ ## Testing
202
+
203
+ ```bash
204
+ pnpm --filter @suimpp/mpp test # 13 tests
205
+ pnpm --filter @suimpp/mpp typecheck
206
+ ```
207
+
208
+ ## License
209
+
210
+ MIT — see [LICENSE](https://github.com/mission69b/suimpp/blob/main/LICENSE)
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ var mppx = require('mppx');
4
+ var transactions = require('@mysten/sui/transactions');
5
+
6
+ // src/client.ts
7
+ var suiCharge = mppx.Method.from({
8
+ intent: "charge",
9
+ name: "sui",
10
+ schema: {
11
+ credential: {
12
+ payload: mppx.z.object({
13
+ digest: mppx.z.string()
14
+ })
15
+ },
16
+ request: mppx.z.object({
17
+ amount: mppx.z.string(),
18
+ currency: mppx.z.string(),
19
+ recipient: mppx.z.string()
20
+ })
21
+ }
22
+ });
23
+
24
+ // src/utils.ts
25
+ function parseAmountToRaw(amount, decimals) {
26
+ const [whole = "0", frac = ""] = amount.split(".");
27
+ const paddedFrac = frac.padEnd(decimals, "0").slice(0, decimals);
28
+ return BigInt(whole + paddedFrac);
29
+ }
30
+
31
+ // src/constants.ts
32
+ var SUI_USDC_TYPE = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC";
33
+
34
+ // src/client.ts
35
+ function sui(options) {
36
+ const address = options.signer.toSuiAddress();
37
+ const decimals = options.decimals ?? 6;
38
+ return mppx.Method.toClient(suiCharge, {
39
+ async createCredential({ challenge }) {
40
+ const { amount, currency, recipient } = challenge.request;
41
+ const amountRaw = parseAmountToRaw(amount, decimals);
42
+ const tx = new transactions.Transaction();
43
+ tx.setSender(address);
44
+ const payment = transactions.coinWithBalance({ balance: amountRaw, type: currency });
45
+ tx.transferObjects([payment], recipient);
46
+ let result;
47
+ try {
48
+ if (options.execute) {
49
+ result = await options.execute(tx);
50
+ } else {
51
+ const built = await tx.build({ client: options.client });
52
+ const { signature } = await options.signer.signTransaction(built);
53
+ const execResult = await options.client.core.executeTransaction({
54
+ transaction: built,
55
+ signatures: [signature],
56
+ include: { effects: true }
57
+ });
58
+ if (execResult.FailedTransaction) {
59
+ throw new Error(execResult.FailedTransaction.status.error?.message ?? "Transaction failed");
60
+ }
61
+ result = execResult.Transaction;
62
+ }
63
+ } catch (err) {
64
+ const msg = err instanceof Error ? err.message : String(err);
65
+ throw new Error(`Payment transaction failed: ${msg}`);
66
+ }
67
+ return mppx.Credential.serialize({
68
+ challenge,
69
+ payload: { digest: result.digest }
70
+ });
71
+ }
72
+ });
73
+ }
74
+
75
+ exports.SUI_USDC_TYPE = SUI_USDC_TYPE;
76
+ exports.sui = sui;
77
+ exports.suiCharge = suiCharge;
78
+ //# sourceMappingURL=client.cjs.map
79
+ //# sourceMappingURL=client.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/method.ts","../src/utils.ts","../src/constants.ts","../src/client.ts"],"names":["Method","z","Transaction","coinWithBalance","Credential"],"mappings":"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","file":"client.cjs","sourcesContent":["import { Method, z } from 'mppx';\n\nexport const suiCharge = Method.from({\n intent: 'charge',\n name: 'sui',\n schema: {\n credential: {\n payload: z.object({\n digest: z.string(),\n }),\n },\n request: z.object({\n amount: z.string(),\n currency: z.string(),\n recipient: z.string(),\n }),\n },\n});\n","/**\n * Parse a string amount to raw bigint units without floating-point math.\n * \"0.01\" with 6 decimals → 10000n\n */\nexport function parseAmountToRaw(amount: string, decimals: number): bigint {\n const [whole = '0', frac = ''] = amount.split('.');\n const paddedFrac = frac.padEnd(decimals, '0').slice(0, decimals);\n return BigInt(whole + paddedFrac);\n}\n\n/**\n * Retry an async function with linear backoff.\n * Throws the last error if all attempts fail.\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n { attempts = 5, baseDelayMs = 1000 }: { attempts?: number; baseDelayMs?: number } = {},\n): Promise<T> {\n let lastError: unknown;\n for (let i = 0; i < attempts; i++) {\n try {\n return await fn();\n } catch (err) {\n lastError = err;\n if (i < attempts - 1) {\n await new Promise((r) => setTimeout(r, baseDelayMs * (i + 1)));\n }\n }\n }\n throw lastError;\n}\n","export const SUI_USDC_TYPE =\n '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC';\n","import { Method, Credential } from 'mppx';\nimport type { ClientWithCoreApi } from '@mysten/sui/client';\nimport type { Signer } from '@mysten/sui/cryptography';\nimport { coinWithBalance, Transaction } from '@mysten/sui/transactions';\nimport { suiCharge } from './method.js';\nimport { parseAmountToRaw } from './utils.js';\n\nexport { suiCharge } from './method.js';\nexport { SUI_USDC_TYPE } from './constants.js';\n\nexport interface SuiChargeOptions {\n client: ClientWithCoreApi;\n signer: Signer;\n /** Number of decimal places for the currency (default: 6, e.g. USDC). */\n decimals?: number;\n /** Override transaction execution (e.g. to route through a gas manager). */\n execute?: (tx: Transaction) => Promise<{ digest: string }>;\n}\n\nexport function sui(options: SuiChargeOptions) {\n const address = options.signer.toSuiAddress();\n const decimals = options.decimals ?? 6;\n\n return Method.toClient(suiCharge, {\n async createCredential({ challenge }) {\n const { amount, currency, recipient } = challenge.request;\n const amountRaw = parseAmountToRaw(amount, decimals);\n\n const tx = new Transaction();\n tx.setSender(address);\n\n const payment = coinWithBalance({ balance: amountRaw, type: currency });\n tx.transferObjects([payment], recipient);\n\n let result;\n try {\n if (options.execute) {\n result = await options.execute(tx);\n } else {\n const built = await tx.build({ client: options.client });\n const { signature } = await options.signer.signTransaction(built);\n const execResult = await options.client.core.executeTransaction({\n transaction: built,\n signatures: [signature],\n include: { effects: true },\n });\n if (execResult.FailedTransaction) {\n throw new Error(execResult.FailedTransaction.status.error?.message ?? 'Transaction failed');\n }\n result = execResult.Transaction!;\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Payment transaction failed: ${msg}`);\n }\n\n return Credential.serialize({\n challenge,\n payload: { digest: result.digest },\n });\n },\n });\n}\n"]}
@@ -0,0 +1,36 @@
1
+ import * as zod_v4_core from 'zod/v4/core';
2
+ import * as zod_mini from 'zod/mini';
3
+ import { Method } from 'mppx';
4
+ import { ClientWithCoreApi } from '@mysten/sui/client';
5
+ import { Signer } from '@mysten/sui/cryptography';
6
+ import { Transaction } from '@mysten/sui/transactions';
7
+ export { S as SUI_USDC_TYPE, s as suiCharge } from './constants-D31h0GdU.cjs';
8
+
9
+ interface SuiChargeOptions {
10
+ client: ClientWithCoreApi;
11
+ signer: Signer;
12
+ /** Number of decimal places for the currency (default: 6, e.g. USDC). */
13
+ decimals?: number;
14
+ /** Override transaction execution (e.g. to route through a gas manager). */
15
+ execute?: (tx: Transaction) => Promise<{
16
+ digest: string;
17
+ }>;
18
+ }
19
+ declare function sui(options: SuiChargeOptions): Method.Client<{
20
+ readonly intent: "charge";
21
+ readonly name: "sui";
22
+ readonly schema: {
23
+ readonly credential: {
24
+ readonly payload: zod_mini.ZodMiniObject<{
25
+ digest: zod_mini.ZodMiniString<string>;
26
+ }, zod_v4_core.$strip>;
27
+ };
28
+ readonly request: zod_mini.ZodMiniObject<{
29
+ amount: zod_mini.ZodMiniString<string>;
30
+ currency: zod_mini.ZodMiniString<string>;
31
+ recipient: zod_mini.ZodMiniString<string>;
32
+ }, zod_v4_core.$strip>;
33
+ };
34
+ }, undefined>;
35
+
36
+ export { type SuiChargeOptions, sui };
@@ -0,0 +1,36 @@
1
+ import * as zod_v4_core from 'zod/v4/core';
2
+ import * as zod_mini from 'zod/mini';
3
+ import { Method } from 'mppx';
4
+ import { ClientWithCoreApi } from '@mysten/sui/client';
5
+ import { Signer } from '@mysten/sui/cryptography';
6
+ import { Transaction } from '@mysten/sui/transactions';
7
+ export { S as SUI_USDC_TYPE, s as suiCharge } from './constants-D31h0GdU.js';
8
+
9
+ interface SuiChargeOptions {
10
+ client: ClientWithCoreApi;
11
+ signer: Signer;
12
+ /** Number of decimal places for the currency (default: 6, e.g. USDC). */
13
+ decimals?: number;
14
+ /** Override transaction execution (e.g. to route through a gas manager). */
15
+ execute?: (tx: Transaction) => Promise<{
16
+ digest: string;
17
+ }>;
18
+ }
19
+ declare function sui(options: SuiChargeOptions): Method.Client<{
20
+ readonly intent: "charge";
21
+ readonly name: "sui";
22
+ readonly schema: {
23
+ readonly credential: {
24
+ readonly payload: zod_mini.ZodMiniObject<{
25
+ digest: zod_mini.ZodMiniString<string>;
26
+ }, zod_v4_core.$strip>;
27
+ };
28
+ readonly request: zod_mini.ZodMiniObject<{
29
+ amount: zod_mini.ZodMiniString<string>;
30
+ currency: zod_mini.ZodMiniString<string>;
31
+ recipient: zod_mini.ZodMiniString<string>;
32
+ }, zod_v4_core.$strip>;
33
+ };
34
+ }, undefined>;
35
+
36
+ export { type SuiChargeOptions, sui };
package/dist/client.js ADDED
@@ -0,0 +1,75 @@
1
+ import { Method, z, Credential } from 'mppx';
2
+ import { Transaction, coinWithBalance } from '@mysten/sui/transactions';
3
+
4
+ // src/client.ts
5
+ var suiCharge = Method.from({
6
+ intent: "charge",
7
+ name: "sui",
8
+ schema: {
9
+ credential: {
10
+ payload: z.object({
11
+ digest: z.string()
12
+ })
13
+ },
14
+ request: z.object({
15
+ amount: z.string(),
16
+ currency: z.string(),
17
+ recipient: z.string()
18
+ })
19
+ }
20
+ });
21
+
22
+ // src/utils.ts
23
+ function parseAmountToRaw(amount, decimals) {
24
+ const [whole = "0", frac = ""] = amount.split(".");
25
+ const paddedFrac = frac.padEnd(decimals, "0").slice(0, decimals);
26
+ return BigInt(whole + paddedFrac);
27
+ }
28
+
29
+ // src/constants.ts
30
+ var SUI_USDC_TYPE = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC";
31
+
32
+ // src/client.ts
33
+ function sui(options) {
34
+ const address = options.signer.toSuiAddress();
35
+ const decimals = options.decimals ?? 6;
36
+ return Method.toClient(suiCharge, {
37
+ async createCredential({ challenge }) {
38
+ const { amount, currency, recipient } = challenge.request;
39
+ const amountRaw = parseAmountToRaw(amount, decimals);
40
+ const tx = new Transaction();
41
+ tx.setSender(address);
42
+ const payment = coinWithBalance({ balance: amountRaw, type: currency });
43
+ tx.transferObjects([payment], recipient);
44
+ let result;
45
+ try {
46
+ if (options.execute) {
47
+ result = await options.execute(tx);
48
+ } else {
49
+ const built = await tx.build({ client: options.client });
50
+ const { signature } = await options.signer.signTransaction(built);
51
+ const execResult = await options.client.core.executeTransaction({
52
+ transaction: built,
53
+ signatures: [signature],
54
+ include: { effects: true }
55
+ });
56
+ if (execResult.FailedTransaction) {
57
+ throw new Error(execResult.FailedTransaction.status.error?.message ?? "Transaction failed");
58
+ }
59
+ result = execResult.Transaction;
60
+ }
61
+ } catch (err) {
62
+ const msg = err instanceof Error ? err.message : String(err);
63
+ throw new Error(`Payment transaction failed: ${msg}`);
64
+ }
65
+ return Credential.serialize({
66
+ challenge,
67
+ payload: { digest: result.digest }
68
+ });
69
+ }
70
+ });
71
+ }
72
+
73
+ export { SUI_USDC_TYPE, sui, suiCharge };
74
+ //# sourceMappingURL=client.js.map
75
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/method.ts","../src/utils.ts","../src/constants.ts","../src/client.ts"],"names":["Method"],"mappings":"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","file":"client.js","sourcesContent":["import { Method, z } from 'mppx';\n\nexport const suiCharge = Method.from({\n intent: 'charge',\n name: 'sui',\n schema: {\n credential: {\n payload: z.object({\n digest: z.string(),\n }),\n },\n request: z.object({\n amount: z.string(),\n currency: z.string(),\n recipient: z.string(),\n }),\n },\n});\n","/**\n * Parse a string amount to raw bigint units without floating-point math.\n * \"0.01\" with 6 decimals → 10000n\n */\nexport function parseAmountToRaw(amount: string, decimals: number): bigint {\n const [whole = '0', frac = ''] = amount.split('.');\n const paddedFrac = frac.padEnd(decimals, '0').slice(0, decimals);\n return BigInt(whole + paddedFrac);\n}\n\n/**\n * Retry an async function with linear backoff.\n * Throws the last error if all attempts fail.\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n { attempts = 5, baseDelayMs = 1000 }: { attempts?: number; baseDelayMs?: number } = {},\n): Promise<T> {\n let lastError: unknown;\n for (let i = 0; i < attempts; i++) {\n try {\n return await fn();\n } catch (err) {\n lastError = err;\n if (i < attempts - 1) {\n await new Promise((r) => setTimeout(r, baseDelayMs * (i + 1)));\n }\n }\n }\n throw lastError;\n}\n","export const SUI_USDC_TYPE =\n '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC';\n","import { Method, Credential } from 'mppx';\nimport type { ClientWithCoreApi } from '@mysten/sui/client';\nimport type { Signer } from '@mysten/sui/cryptography';\nimport { coinWithBalance, Transaction } from '@mysten/sui/transactions';\nimport { suiCharge } from './method.js';\nimport { parseAmountToRaw } from './utils.js';\n\nexport { suiCharge } from './method.js';\nexport { SUI_USDC_TYPE } from './constants.js';\n\nexport interface SuiChargeOptions {\n client: ClientWithCoreApi;\n signer: Signer;\n /** Number of decimal places for the currency (default: 6, e.g. USDC). */\n decimals?: number;\n /** Override transaction execution (e.g. to route through a gas manager). */\n execute?: (tx: Transaction) => Promise<{ digest: string }>;\n}\n\nexport function sui(options: SuiChargeOptions) {\n const address = options.signer.toSuiAddress();\n const decimals = options.decimals ?? 6;\n\n return Method.toClient(suiCharge, {\n async createCredential({ challenge }) {\n const { amount, currency, recipient } = challenge.request;\n const amountRaw = parseAmountToRaw(amount, decimals);\n\n const tx = new Transaction();\n tx.setSender(address);\n\n const payment = coinWithBalance({ balance: amountRaw, type: currency });\n tx.transferObjects([payment], recipient);\n\n let result;\n try {\n if (options.execute) {\n result = await options.execute(tx);\n } else {\n const built = await tx.build({ client: options.client });\n const { signature } = await options.signer.signTransaction(built);\n const execResult = await options.client.core.executeTransaction({\n transaction: built,\n signatures: [signature],\n include: { effects: true },\n });\n if (execResult.FailedTransaction) {\n throw new Error(execResult.FailedTransaction.status.error?.message ?? 'Transaction failed');\n }\n result = execResult.Transaction!;\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Payment transaction failed: ${msg}`);\n }\n\n return Credential.serialize({\n challenge,\n payload: { digest: result.digest },\n });\n },\n });\n}\n"]}
@@ -0,0 +1,22 @@
1
+ import { z } from 'mppx';
2
+
3
+ declare const suiCharge: {
4
+ readonly intent: "charge";
5
+ readonly name: "sui";
6
+ readonly schema: {
7
+ readonly credential: {
8
+ readonly payload: z.ZodMiniObject<{
9
+ digest: z.ZodMiniString<string>;
10
+ }, z.core.$strip>;
11
+ };
12
+ readonly request: z.ZodMiniObject<{
13
+ amount: z.ZodMiniString<string>;
14
+ currency: z.ZodMiniString<string>;
15
+ recipient: z.ZodMiniString<string>;
16
+ }, z.core.$strip>;
17
+ };
18
+ };
19
+
20
+ declare const SUI_USDC_TYPE = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC";
21
+
22
+ export { SUI_USDC_TYPE as S, suiCharge as s };
@@ -0,0 +1,22 @@
1
+ import { z } from 'mppx';
2
+
3
+ declare const suiCharge: {
4
+ readonly intent: "charge";
5
+ readonly name: "sui";
6
+ readonly schema: {
7
+ readonly credential: {
8
+ readonly payload: z.ZodMiniObject<{
9
+ digest: z.ZodMiniString<string>;
10
+ }, z.core.$strip>;
11
+ };
12
+ readonly request: z.ZodMiniObject<{
13
+ amount: z.ZodMiniString<string>;
14
+ currency: z.ZodMiniString<string>;
15
+ recipient: z.ZodMiniString<string>;
16
+ }, z.core.$strip>;
17
+ };
18
+ };
19
+
20
+ declare const SUI_USDC_TYPE = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC";
21
+
22
+ export { SUI_USDC_TYPE as S, suiCharge as s };
package/dist/index.cjs ADDED
@@ -0,0 +1,146 @@
1
+ 'use strict';
2
+
3
+ var mppx = require('mppx');
4
+ var transactions = require('@mysten/sui/transactions');
5
+ var grpc = require('@mysten/sui/grpc');
6
+ var utils = require('@mysten/sui/utils');
7
+
8
+ // src/method.ts
9
+ var suiCharge = mppx.Method.from({
10
+ intent: "charge",
11
+ name: "sui",
12
+ schema: {
13
+ credential: {
14
+ payload: mppx.z.object({
15
+ digest: mppx.z.string()
16
+ })
17
+ },
18
+ request: mppx.z.object({
19
+ amount: mppx.z.string(),
20
+ currency: mppx.z.string(),
21
+ recipient: mppx.z.string()
22
+ })
23
+ }
24
+ });
25
+
26
+ // src/utils.ts
27
+ function parseAmountToRaw(amount, decimals) {
28
+ const [whole = "0", frac = ""] = amount.split(".");
29
+ const paddedFrac = frac.padEnd(decimals, "0").slice(0, decimals);
30
+ return BigInt(whole + paddedFrac);
31
+ }
32
+ async function withRetry(fn, { attempts = 5, baseDelayMs = 1e3 } = {}) {
33
+ let lastError;
34
+ for (let i = 0; i < attempts; i++) {
35
+ try {
36
+ return await fn();
37
+ } catch (err) {
38
+ lastError = err;
39
+ if (i < attempts - 1) {
40
+ await new Promise((r) => setTimeout(r, baseDelayMs * (i + 1)));
41
+ }
42
+ }
43
+ }
44
+ throw lastError;
45
+ }
46
+
47
+ // src/constants.ts
48
+ var SUI_USDC_TYPE = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC";
49
+
50
+ // src/client.ts
51
+ function sui(options) {
52
+ const address = options.signer.toSuiAddress();
53
+ const decimals = options.decimals ?? 6;
54
+ return mppx.Method.toClient(suiCharge, {
55
+ async createCredential({ challenge }) {
56
+ const { amount, currency, recipient } = challenge.request;
57
+ const amountRaw = parseAmountToRaw(amount, decimals);
58
+ const tx = new transactions.Transaction();
59
+ tx.setSender(address);
60
+ const payment = transactions.coinWithBalance({ balance: amountRaw, type: currency });
61
+ tx.transferObjects([payment], recipient);
62
+ let result;
63
+ try {
64
+ if (options.execute) {
65
+ result = await options.execute(tx);
66
+ } else {
67
+ const built = await tx.build({ client: options.client });
68
+ const { signature } = await options.signer.signTransaction(built);
69
+ const execResult = await options.client.core.executeTransaction({
70
+ transaction: built,
71
+ signatures: [signature],
72
+ include: { effects: true }
73
+ });
74
+ if (execResult.FailedTransaction) {
75
+ throw new Error(execResult.FailedTransaction.status.error?.message ?? "Transaction failed");
76
+ }
77
+ result = execResult.Transaction;
78
+ }
79
+ } catch (err) {
80
+ const msg = err instanceof Error ? err.message : String(err);
81
+ throw new Error(`Payment transaction failed: ${msg}`);
82
+ }
83
+ return mppx.Credential.serialize({
84
+ challenge,
85
+ payload: { digest: result.digest }
86
+ });
87
+ }
88
+ });
89
+ }
90
+ function sui2(options) {
91
+ const network = options.network ?? "mainnet";
92
+ const decimals = options.decimals ?? 6;
93
+ const client = new grpc.SuiGrpcClient({
94
+ baseUrl: options.rpcUrl ?? `https://fullnode.${network}.sui.io:443`,
95
+ network
96
+ });
97
+ const normalizedRecipient = utils.normalizeSuiAddress(options.recipient);
98
+ return mppx.Method.toServer(suiCharge, {
99
+ defaults: {
100
+ currency: options.currency,
101
+ recipient: options.recipient
102
+ },
103
+ async verify({ credential }) {
104
+ const digest = credential.payload.digest;
105
+ const tx = await withRetry(
106
+ () => client.core.getTransaction({ digest, include: { balanceChanges: true } })
107
+ ).catch(() => {
108
+ throw new Error(`Could not find the referenced transaction [${digest}]`);
109
+ });
110
+ const resolved = tx.Transaction ?? tx.FailedTransaction;
111
+ if (!resolved?.status.success) {
112
+ throw new Error("Transaction failed on-chain");
113
+ }
114
+ const payment = resolved.balanceChanges.find(
115
+ (bc) => bc.coinType === options.currency && utils.normalizeSuiAddress(bc.address) === normalizedRecipient && BigInt(bc.amount) > 0n
116
+ );
117
+ if (!payment) {
118
+ throw new Error(
119
+ "Payment not found in transaction balance changes"
120
+ );
121
+ }
122
+ const transferredRaw = BigInt(payment.amount);
123
+ const requestedRaw = parseAmountToRaw(credential.challenge.request.amount, decimals);
124
+ if (transferredRaw < requestedRaw) {
125
+ throw new Error(
126
+ `Transferred ${transferredRaw} < requested ${requestedRaw} (raw units)`
127
+ );
128
+ }
129
+ return mppx.Receipt.from({
130
+ method: "sui",
131
+ reference: credential.payload.digest,
132
+ status: "success",
133
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
134
+ });
135
+ }
136
+ });
137
+ }
138
+
139
+ exports.SUI_USDC_TYPE = SUI_USDC_TYPE;
140
+ exports.parseAmountToRaw = parseAmountToRaw;
141
+ exports.suiCharge = suiCharge;
142
+ exports.suiClient = sui;
143
+ exports.suiServer = sui2;
144
+ exports.withRetry = withRetry;
145
+ //# sourceMappingURL=index.cjs.map
146
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/method.ts","../src/utils.ts","../src/constants.ts","../src/client.ts","../src/server.ts"],"names":["Method","z","Transaction","coinWithBalance","Credential","sui","SuiGrpcClient","normalizeSuiAddress","Receipt"],"mappings":"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","file":"index.cjs","sourcesContent":["import { Method, z } from 'mppx';\n\nexport const suiCharge = Method.from({\n intent: 'charge',\n name: 'sui',\n schema: {\n credential: {\n payload: z.object({\n digest: z.string(),\n }),\n },\n request: z.object({\n amount: z.string(),\n currency: z.string(),\n recipient: z.string(),\n }),\n },\n});\n","/**\n * Parse a string amount to raw bigint units without floating-point math.\n * \"0.01\" with 6 decimals → 10000n\n */\nexport function parseAmountToRaw(amount: string, decimals: number): bigint {\n const [whole = '0', frac = ''] = amount.split('.');\n const paddedFrac = frac.padEnd(decimals, '0').slice(0, decimals);\n return BigInt(whole + paddedFrac);\n}\n\n/**\n * Retry an async function with linear backoff.\n * Throws the last error if all attempts fail.\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n { attempts = 5, baseDelayMs = 1000 }: { attempts?: number; baseDelayMs?: number } = {},\n): Promise<T> {\n let lastError: unknown;\n for (let i = 0; i < attempts; i++) {\n try {\n return await fn();\n } catch (err) {\n lastError = err;\n if (i < attempts - 1) {\n await new Promise((r) => setTimeout(r, baseDelayMs * (i + 1)));\n }\n }\n }\n throw lastError;\n}\n","export const SUI_USDC_TYPE =\n '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC';\n","import { Method, Credential } from 'mppx';\nimport type { ClientWithCoreApi } from '@mysten/sui/client';\nimport type { Signer } from '@mysten/sui/cryptography';\nimport { coinWithBalance, Transaction } from '@mysten/sui/transactions';\nimport { suiCharge } from './method.js';\nimport { parseAmountToRaw } from './utils.js';\n\nexport { suiCharge } from './method.js';\nexport { SUI_USDC_TYPE } from './constants.js';\n\nexport interface SuiChargeOptions {\n client: ClientWithCoreApi;\n signer: Signer;\n /** Number of decimal places for the currency (default: 6, e.g. USDC). */\n decimals?: number;\n /** Override transaction execution (e.g. to route through a gas manager). */\n execute?: (tx: Transaction) => Promise<{ digest: string }>;\n}\n\nexport function sui(options: SuiChargeOptions) {\n const address = options.signer.toSuiAddress();\n const decimals = options.decimals ?? 6;\n\n return Method.toClient(suiCharge, {\n async createCredential({ challenge }) {\n const { amount, currency, recipient } = challenge.request;\n const amountRaw = parseAmountToRaw(amount, decimals);\n\n const tx = new Transaction();\n tx.setSender(address);\n\n const payment = coinWithBalance({ balance: amountRaw, type: currency });\n tx.transferObjects([payment], recipient);\n\n let result;\n try {\n if (options.execute) {\n result = await options.execute(tx);\n } else {\n const built = await tx.build({ client: options.client });\n const { signature } = await options.signer.signTransaction(built);\n const execResult = await options.client.core.executeTransaction({\n transaction: built,\n signatures: [signature],\n include: { effects: true },\n });\n if (execResult.FailedTransaction) {\n throw new Error(execResult.FailedTransaction.status.error?.message ?? 'Transaction failed');\n }\n result = execResult.Transaction!;\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Payment transaction failed: ${msg}`);\n }\n\n return Credential.serialize({\n challenge,\n payload: { digest: result.digest },\n });\n },\n });\n}\n","import { Method, Receipt } from 'mppx';\nimport { SuiGrpcClient } from '@mysten/sui/grpc';\nimport { normalizeSuiAddress } from '@mysten/sui/utils';\nimport { suiCharge } from './method.js';\nimport { parseAmountToRaw, withRetry } from './utils.js';\n\nexport { suiCharge } from './method.js';\nexport { SUI_USDC_TYPE } from './constants.js';\n\nexport interface SuiServerOptions {\n currency: string;\n recipient: string;\n /** Number of decimal places for the currency (default: 6, e.g. USDC). */\n decimals?: number;\n rpcUrl?: string;\n network?: 'mainnet' | 'testnet' | 'devnet';\n}\n\nexport function sui(options: SuiServerOptions) {\n const network = options.network ?? 'mainnet';\n const decimals = options.decimals ?? 6;\n const client = new SuiGrpcClient({\n baseUrl: options.rpcUrl ?? `https://fullnode.${network}.sui.io:443`,\n network,\n });\n\n const normalizedRecipient = normalizeSuiAddress(options.recipient);\n\n return Method.toServer(suiCharge, {\n defaults: {\n currency: options.currency,\n recipient: options.recipient,\n },\n\n async verify({ credential }) {\n const digest = credential.payload.digest;\n\n const tx = await withRetry(\n () => client.core.getTransaction({ digest, include: { balanceChanges: true } }),\n ).catch(() => {\n throw new Error(`Could not find the referenced transaction [${digest}]`);\n });\n\n const resolved = tx.Transaction ?? tx.FailedTransaction;\n if (!resolved?.status.success) {\n throw new Error('Transaction failed on-chain');\n }\n\n const payment = resolved.balanceChanges.find(\n (bc) =>\n bc.coinType === options.currency &&\n normalizeSuiAddress(bc.address) === normalizedRecipient &&\n BigInt(bc.amount) > 0n,\n );\n\n if (!payment) {\n throw new Error(\n 'Payment not found in transaction balance changes',\n );\n }\n\n const transferredRaw = BigInt(payment.amount);\n const requestedRaw = parseAmountToRaw(credential.challenge.request.amount, decimals);\n if (transferredRaw < requestedRaw) {\n throw new Error(\n `Transferred ${transferredRaw} < requested ${requestedRaw} (raw units)`,\n );\n }\n\n return Receipt.from({\n method: 'sui',\n reference: credential.payload.digest,\n status: 'success',\n timestamp: new Date().toISOString(),\n });\n },\n });\n}\n"]}
@@ -0,0 +1,25 @@
1
+ export { S as SUI_USDC_TYPE, s as suiCharge } from './constants-D31h0GdU.cjs';
2
+ export { SuiChargeOptions, sui as suiClient } from './client.cjs';
3
+ export { SuiServerOptions, sui as suiServer } from './server.cjs';
4
+ import 'mppx';
5
+ import 'zod/v4/core';
6
+ import 'zod/mini';
7
+ import '@mysten/sui/client';
8
+ import '@mysten/sui/cryptography';
9
+ import '@mysten/sui/transactions';
10
+
11
+ /**
12
+ * Parse a string amount to raw bigint units without floating-point math.
13
+ * "0.01" with 6 decimals → 10000n
14
+ */
15
+ declare function parseAmountToRaw(amount: string, decimals: number): bigint;
16
+ /**
17
+ * Retry an async function with linear backoff.
18
+ * Throws the last error if all attempts fail.
19
+ */
20
+ declare function withRetry<T>(fn: () => Promise<T>, { attempts, baseDelayMs }?: {
21
+ attempts?: number;
22
+ baseDelayMs?: number;
23
+ }): Promise<T>;
24
+
25
+ export { parseAmountToRaw, withRetry };
@@ -0,0 +1,25 @@
1
+ export { S as SUI_USDC_TYPE, s as suiCharge } from './constants-D31h0GdU.js';
2
+ export { SuiChargeOptions, sui as suiClient } from './client.js';
3
+ export { SuiServerOptions, sui as suiServer } from './server.js';
4
+ import 'mppx';
5
+ import 'zod/v4/core';
6
+ import 'zod/mini';
7
+ import '@mysten/sui/client';
8
+ import '@mysten/sui/cryptography';
9
+ import '@mysten/sui/transactions';
10
+
11
+ /**
12
+ * Parse a string amount to raw bigint units without floating-point math.
13
+ * "0.01" with 6 decimals → 10000n
14
+ */
15
+ declare function parseAmountToRaw(amount: string, decimals: number): bigint;
16
+ /**
17
+ * Retry an async function with linear backoff.
18
+ * Throws the last error if all attempts fail.
19
+ */
20
+ declare function withRetry<T>(fn: () => Promise<T>, { attempts, baseDelayMs }?: {
21
+ attempts?: number;
22
+ baseDelayMs?: number;
23
+ }): Promise<T>;
24
+
25
+ export { parseAmountToRaw, withRetry };
package/dist/index.js ADDED
@@ -0,0 +1,139 @@
1
+ import { Method, z, Credential, Receipt } from 'mppx';
2
+ import { Transaction, coinWithBalance } from '@mysten/sui/transactions';
3
+ import { SuiGrpcClient } from '@mysten/sui/grpc';
4
+ import { normalizeSuiAddress } from '@mysten/sui/utils';
5
+
6
+ // src/method.ts
7
+ var suiCharge = Method.from({
8
+ intent: "charge",
9
+ name: "sui",
10
+ schema: {
11
+ credential: {
12
+ payload: z.object({
13
+ digest: z.string()
14
+ })
15
+ },
16
+ request: z.object({
17
+ amount: z.string(),
18
+ currency: z.string(),
19
+ recipient: z.string()
20
+ })
21
+ }
22
+ });
23
+
24
+ // src/utils.ts
25
+ function parseAmountToRaw(amount, decimals) {
26
+ const [whole = "0", frac = ""] = amount.split(".");
27
+ const paddedFrac = frac.padEnd(decimals, "0").slice(0, decimals);
28
+ return BigInt(whole + paddedFrac);
29
+ }
30
+ async function withRetry(fn, { attempts = 5, baseDelayMs = 1e3 } = {}) {
31
+ let lastError;
32
+ for (let i = 0; i < attempts; i++) {
33
+ try {
34
+ return await fn();
35
+ } catch (err) {
36
+ lastError = err;
37
+ if (i < attempts - 1) {
38
+ await new Promise((r) => setTimeout(r, baseDelayMs * (i + 1)));
39
+ }
40
+ }
41
+ }
42
+ throw lastError;
43
+ }
44
+
45
+ // src/constants.ts
46
+ var SUI_USDC_TYPE = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC";
47
+
48
+ // src/client.ts
49
+ function sui(options) {
50
+ const address = options.signer.toSuiAddress();
51
+ const decimals = options.decimals ?? 6;
52
+ return Method.toClient(suiCharge, {
53
+ async createCredential({ challenge }) {
54
+ const { amount, currency, recipient } = challenge.request;
55
+ const amountRaw = parseAmountToRaw(amount, decimals);
56
+ const tx = new Transaction();
57
+ tx.setSender(address);
58
+ const payment = coinWithBalance({ balance: amountRaw, type: currency });
59
+ tx.transferObjects([payment], recipient);
60
+ let result;
61
+ try {
62
+ if (options.execute) {
63
+ result = await options.execute(tx);
64
+ } else {
65
+ const built = await tx.build({ client: options.client });
66
+ const { signature } = await options.signer.signTransaction(built);
67
+ const execResult = await options.client.core.executeTransaction({
68
+ transaction: built,
69
+ signatures: [signature],
70
+ include: { effects: true }
71
+ });
72
+ if (execResult.FailedTransaction) {
73
+ throw new Error(execResult.FailedTransaction.status.error?.message ?? "Transaction failed");
74
+ }
75
+ result = execResult.Transaction;
76
+ }
77
+ } catch (err) {
78
+ const msg = err instanceof Error ? err.message : String(err);
79
+ throw new Error(`Payment transaction failed: ${msg}`);
80
+ }
81
+ return Credential.serialize({
82
+ challenge,
83
+ payload: { digest: result.digest }
84
+ });
85
+ }
86
+ });
87
+ }
88
+ function sui2(options) {
89
+ const network = options.network ?? "mainnet";
90
+ const decimals = options.decimals ?? 6;
91
+ const client = new SuiGrpcClient({
92
+ baseUrl: options.rpcUrl ?? `https://fullnode.${network}.sui.io:443`,
93
+ network
94
+ });
95
+ const normalizedRecipient = normalizeSuiAddress(options.recipient);
96
+ return Method.toServer(suiCharge, {
97
+ defaults: {
98
+ currency: options.currency,
99
+ recipient: options.recipient
100
+ },
101
+ async verify({ credential }) {
102
+ const digest = credential.payload.digest;
103
+ const tx = await withRetry(
104
+ () => client.core.getTransaction({ digest, include: { balanceChanges: true } })
105
+ ).catch(() => {
106
+ throw new Error(`Could not find the referenced transaction [${digest}]`);
107
+ });
108
+ const resolved = tx.Transaction ?? tx.FailedTransaction;
109
+ if (!resolved?.status.success) {
110
+ throw new Error("Transaction failed on-chain");
111
+ }
112
+ const payment = resolved.balanceChanges.find(
113
+ (bc) => bc.coinType === options.currency && normalizeSuiAddress(bc.address) === normalizedRecipient && BigInt(bc.amount) > 0n
114
+ );
115
+ if (!payment) {
116
+ throw new Error(
117
+ "Payment not found in transaction balance changes"
118
+ );
119
+ }
120
+ const transferredRaw = BigInt(payment.amount);
121
+ const requestedRaw = parseAmountToRaw(credential.challenge.request.amount, decimals);
122
+ if (transferredRaw < requestedRaw) {
123
+ throw new Error(
124
+ `Transferred ${transferredRaw} < requested ${requestedRaw} (raw units)`
125
+ );
126
+ }
127
+ return Receipt.from({
128
+ method: "sui",
129
+ reference: credential.payload.digest,
130
+ status: "success",
131
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
132
+ });
133
+ }
134
+ });
135
+ }
136
+
137
+ export { SUI_USDC_TYPE, parseAmountToRaw, suiCharge, sui as suiClient, sui2 as suiServer, withRetry };
138
+ //# sourceMappingURL=index.js.map
139
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/method.ts","../src/utils.ts","../src/constants.ts","../src/client.ts","../src/server.ts"],"names":["Method","sui"],"mappings":"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","file":"index.js","sourcesContent":["import { Method, z } from 'mppx';\n\nexport const suiCharge = Method.from({\n intent: 'charge',\n name: 'sui',\n schema: {\n credential: {\n payload: z.object({\n digest: z.string(),\n }),\n },\n request: z.object({\n amount: z.string(),\n currency: z.string(),\n recipient: z.string(),\n }),\n },\n});\n","/**\n * Parse a string amount to raw bigint units without floating-point math.\n * \"0.01\" with 6 decimals → 10000n\n */\nexport function parseAmountToRaw(amount: string, decimals: number): bigint {\n const [whole = '0', frac = ''] = amount.split('.');\n const paddedFrac = frac.padEnd(decimals, '0').slice(0, decimals);\n return BigInt(whole + paddedFrac);\n}\n\n/**\n * Retry an async function with linear backoff.\n * Throws the last error if all attempts fail.\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n { attempts = 5, baseDelayMs = 1000 }: { attempts?: number; baseDelayMs?: number } = {},\n): Promise<T> {\n let lastError: unknown;\n for (let i = 0; i < attempts; i++) {\n try {\n return await fn();\n } catch (err) {\n lastError = err;\n if (i < attempts - 1) {\n await new Promise((r) => setTimeout(r, baseDelayMs * (i + 1)));\n }\n }\n }\n throw lastError;\n}\n","export const SUI_USDC_TYPE =\n '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC';\n","import { Method, Credential } from 'mppx';\nimport type { ClientWithCoreApi } from '@mysten/sui/client';\nimport type { Signer } from '@mysten/sui/cryptography';\nimport { coinWithBalance, Transaction } from '@mysten/sui/transactions';\nimport { suiCharge } from './method.js';\nimport { parseAmountToRaw } from './utils.js';\n\nexport { suiCharge } from './method.js';\nexport { SUI_USDC_TYPE } from './constants.js';\n\nexport interface SuiChargeOptions {\n client: ClientWithCoreApi;\n signer: Signer;\n /** Number of decimal places for the currency (default: 6, e.g. USDC). */\n decimals?: number;\n /** Override transaction execution (e.g. to route through a gas manager). */\n execute?: (tx: Transaction) => Promise<{ digest: string }>;\n}\n\nexport function sui(options: SuiChargeOptions) {\n const address = options.signer.toSuiAddress();\n const decimals = options.decimals ?? 6;\n\n return Method.toClient(suiCharge, {\n async createCredential({ challenge }) {\n const { amount, currency, recipient } = challenge.request;\n const amountRaw = parseAmountToRaw(amount, decimals);\n\n const tx = new Transaction();\n tx.setSender(address);\n\n const payment = coinWithBalance({ balance: amountRaw, type: currency });\n tx.transferObjects([payment], recipient);\n\n let result;\n try {\n if (options.execute) {\n result = await options.execute(tx);\n } else {\n const built = await tx.build({ client: options.client });\n const { signature } = await options.signer.signTransaction(built);\n const execResult = await options.client.core.executeTransaction({\n transaction: built,\n signatures: [signature],\n include: { effects: true },\n });\n if (execResult.FailedTransaction) {\n throw new Error(execResult.FailedTransaction.status.error?.message ?? 'Transaction failed');\n }\n result = execResult.Transaction!;\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Payment transaction failed: ${msg}`);\n }\n\n return Credential.serialize({\n challenge,\n payload: { digest: result.digest },\n });\n },\n });\n}\n","import { Method, Receipt } from 'mppx';\nimport { SuiGrpcClient } from '@mysten/sui/grpc';\nimport { normalizeSuiAddress } from '@mysten/sui/utils';\nimport { suiCharge } from './method.js';\nimport { parseAmountToRaw, withRetry } from './utils.js';\n\nexport { suiCharge } from './method.js';\nexport { SUI_USDC_TYPE } from './constants.js';\n\nexport interface SuiServerOptions {\n currency: string;\n recipient: string;\n /** Number of decimal places for the currency (default: 6, e.g. USDC). */\n decimals?: number;\n rpcUrl?: string;\n network?: 'mainnet' | 'testnet' | 'devnet';\n}\n\nexport function sui(options: SuiServerOptions) {\n const network = options.network ?? 'mainnet';\n const decimals = options.decimals ?? 6;\n const client = new SuiGrpcClient({\n baseUrl: options.rpcUrl ?? `https://fullnode.${network}.sui.io:443`,\n network,\n });\n\n const normalizedRecipient = normalizeSuiAddress(options.recipient);\n\n return Method.toServer(suiCharge, {\n defaults: {\n currency: options.currency,\n recipient: options.recipient,\n },\n\n async verify({ credential }) {\n const digest = credential.payload.digest;\n\n const tx = await withRetry(\n () => client.core.getTransaction({ digest, include: { balanceChanges: true } }),\n ).catch(() => {\n throw new Error(`Could not find the referenced transaction [${digest}]`);\n });\n\n const resolved = tx.Transaction ?? tx.FailedTransaction;\n if (!resolved?.status.success) {\n throw new Error('Transaction failed on-chain');\n }\n\n const payment = resolved.balanceChanges.find(\n (bc) =>\n bc.coinType === options.currency &&\n normalizeSuiAddress(bc.address) === normalizedRecipient &&\n BigInt(bc.amount) > 0n,\n );\n\n if (!payment) {\n throw new Error(\n 'Payment not found in transaction balance changes',\n );\n }\n\n const transferredRaw = BigInt(payment.amount);\n const requestedRaw = parseAmountToRaw(credential.challenge.request.amount, decimals);\n if (transferredRaw < requestedRaw) {\n throw new Error(\n `Transferred ${transferredRaw} < requested ${requestedRaw} (raw units)`,\n );\n }\n\n return Receipt.from({\n method: 'sui',\n reference: credential.payload.digest,\n status: 'success',\n timestamp: new Date().toISOString(),\n });\n },\n });\n}\n"]}
@@ -0,0 +1,103 @@
1
+ 'use strict';
2
+
3
+ var mppx = require('mppx');
4
+ var grpc = require('@mysten/sui/grpc');
5
+ var utils = require('@mysten/sui/utils');
6
+
7
+ // src/server.ts
8
+ var suiCharge = mppx.Method.from({
9
+ intent: "charge",
10
+ name: "sui",
11
+ schema: {
12
+ credential: {
13
+ payload: mppx.z.object({
14
+ digest: mppx.z.string()
15
+ })
16
+ },
17
+ request: mppx.z.object({
18
+ amount: mppx.z.string(),
19
+ currency: mppx.z.string(),
20
+ recipient: mppx.z.string()
21
+ })
22
+ }
23
+ });
24
+
25
+ // src/utils.ts
26
+ function parseAmountToRaw(amount, decimals) {
27
+ const [whole = "0", frac = ""] = amount.split(".");
28
+ const paddedFrac = frac.padEnd(decimals, "0").slice(0, decimals);
29
+ return BigInt(whole + paddedFrac);
30
+ }
31
+ async function withRetry(fn, { attempts = 5, baseDelayMs = 1e3 } = {}) {
32
+ let lastError;
33
+ for (let i = 0; i < attempts; i++) {
34
+ try {
35
+ return await fn();
36
+ } catch (err) {
37
+ lastError = err;
38
+ if (i < attempts - 1) {
39
+ await new Promise((r) => setTimeout(r, baseDelayMs * (i + 1)));
40
+ }
41
+ }
42
+ }
43
+ throw lastError;
44
+ }
45
+
46
+ // src/constants.ts
47
+ var SUI_USDC_TYPE = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC";
48
+
49
+ // src/server.ts
50
+ function sui(options) {
51
+ const network = options.network ?? "mainnet";
52
+ const decimals = options.decimals ?? 6;
53
+ const client = new grpc.SuiGrpcClient({
54
+ baseUrl: options.rpcUrl ?? `https://fullnode.${network}.sui.io:443`,
55
+ network
56
+ });
57
+ const normalizedRecipient = utils.normalizeSuiAddress(options.recipient);
58
+ return mppx.Method.toServer(suiCharge, {
59
+ defaults: {
60
+ currency: options.currency,
61
+ recipient: options.recipient
62
+ },
63
+ async verify({ credential }) {
64
+ const digest = credential.payload.digest;
65
+ const tx = await withRetry(
66
+ () => client.core.getTransaction({ digest, include: { balanceChanges: true } })
67
+ ).catch(() => {
68
+ throw new Error(`Could not find the referenced transaction [${digest}]`);
69
+ });
70
+ const resolved = tx.Transaction ?? tx.FailedTransaction;
71
+ if (!resolved?.status.success) {
72
+ throw new Error("Transaction failed on-chain");
73
+ }
74
+ const payment = resolved.balanceChanges.find(
75
+ (bc) => bc.coinType === options.currency && utils.normalizeSuiAddress(bc.address) === normalizedRecipient && BigInt(bc.amount) > 0n
76
+ );
77
+ if (!payment) {
78
+ throw new Error(
79
+ "Payment not found in transaction balance changes"
80
+ );
81
+ }
82
+ const transferredRaw = BigInt(payment.amount);
83
+ const requestedRaw = parseAmountToRaw(credential.challenge.request.amount, decimals);
84
+ if (transferredRaw < requestedRaw) {
85
+ throw new Error(
86
+ `Transferred ${transferredRaw} < requested ${requestedRaw} (raw units)`
87
+ );
88
+ }
89
+ return mppx.Receipt.from({
90
+ method: "sui",
91
+ reference: credential.payload.digest,
92
+ status: "success",
93
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
94
+ });
95
+ }
96
+ });
97
+ }
98
+
99
+ exports.SUI_USDC_TYPE = SUI_USDC_TYPE;
100
+ exports.sui = sui;
101
+ exports.suiCharge = suiCharge;
102
+ //# sourceMappingURL=server.cjs.map
103
+ //# sourceMappingURL=server.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/method.ts","../src/utils.ts","../src/constants.ts","../src/server.ts"],"names":["Method","z","SuiGrpcClient","normalizeSuiAddress","Receipt"],"mappings":"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","file":"server.cjs","sourcesContent":["import { Method, z } from 'mppx';\n\nexport const suiCharge = Method.from({\n intent: 'charge',\n name: 'sui',\n schema: {\n credential: {\n payload: z.object({\n digest: z.string(),\n }),\n },\n request: z.object({\n amount: z.string(),\n currency: z.string(),\n recipient: z.string(),\n }),\n },\n});\n","/**\n * Parse a string amount to raw bigint units without floating-point math.\n * \"0.01\" with 6 decimals → 10000n\n */\nexport function parseAmountToRaw(amount: string, decimals: number): bigint {\n const [whole = '0', frac = ''] = amount.split('.');\n const paddedFrac = frac.padEnd(decimals, '0').slice(0, decimals);\n return BigInt(whole + paddedFrac);\n}\n\n/**\n * Retry an async function with linear backoff.\n * Throws the last error if all attempts fail.\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n { attempts = 5, baseDelayMs = 1000 }: { attempts?: number; baseDelayMs?: number } = {},\n): Promise<T> {\n let lastError: unknown;\n for (let i = 0; i < attempts; i++) {\n try {\n return await fn();\n } catch (err) {\n lastError = err;\n if (i < attempts - 1) {\n await new Promise((r) => setTimeout(r, baseDelayMs * (i + 1)));\n }\n }\n }\n throw lastError;\n}\n","export const SUI_USDC_TYPE =\n '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC';\n","import { Method, Receipt } from 'mppx';\nimport { SuiGrpcClient } from '@mysten/sui/grpc';\nimport { normalizeSuiAddress } from '@mysten/sui/utils';\nimport { suiCharge } from './method.js';\nimport { parseAmountToRaw, withRetry } from './utils.js';\n\nexport { suiCharge } from './method.js';\nexport { SUI_USDC_TYPE } from './constants.js';\n\nexport interface SuiServerOptions {\n currency: string;\n recipient: string;\n /** Number of decimal places for the currency (default: 6, e.g. USDC). */\n decimals?: number;\n rpcUrl?: string;\n network?: 'mainnet' | 'testnet' | 'devnet';\n}\n\nexport function sui(options: SuiServerOptions) {\n const network = options.network ?? 'mainnet';\n const decimals = options.decimals ?? 6;\n const client = new SuiGrpcClient({\n baseUrl: options.rpcUrl ?? `https://fullnode.${network}.sui.io:443`,\n network,\n });\n\n const normalizedRecipient = normalizeSuiAddress(options.recipient);\n\n return Method.toServer(suiCharge, {\n defaults: {\n currency: options.currency,\n recipient: options.recipient,\n },\n\n async verify({ credential }) {\n const digest = credential.payload.digest;\n\n const tx = await withRetry(\n () => client.core.getTransaction({ digest, include: { balanceChanges: true } }),\n ).catch(() => {\n throw new Error(`Could not find the referenced transaction [${digest}]`);\n });\n\n const resolved = tx.Transaction ?? tx.FailedTransaction;\n if (!resolved?.status.success) {\n throw new Error('Transaction failed on-chain');\n }\n\n const payment = resolved.balanceChanges.find(\n (bc) =>\n bc.coinType === options.currency &&\n normalizeSuiAddress(bc.address) === normalizedRecipient &&\n BigInt(bc.amount) > 0n,\n );\n\n if (!payment) {\n throw new Error(\n 'Payment not found in transaction balance changes',\n );\n }\n\n const transferredRaw = BigInt(payment.amount);\n const requestedRaw = parseAmountToRaw(credential.challenge.request.amount, decimals);\n if (transferredRaw < requestedRaw) {\n throw new Error(\n `Transferred ${transferredRaw} < requested ${requestedRaw} (raw units)`,\n );\n }\n\n return Receipt.from({\n method: 'sui',\n reference: credential.payload.digest,\n status: 'success',\n timestamp: new Date().toISOString(),\n });\n },\n });\n}\n"]}
@@ -0,0 +1,34 @@
1
+ import * as zod_v4_core from 'zod/v4/core';
2
+ import * as zod_mini from 'zod/mini';
3
+ import { Method } from 'mppx';
4
+ export { S as SUI_USDC_TYPE, s as suiCharge } from './constants-D31h0GdU.cjs';
5
+
6
+ interface SuiServerOptions {
7
+ currency: string;
8
+ recipient: string;
9
+ /** Number of decimal places for the currency (default: 6, e.g. USDC). */
10
+ decimals?: number;
11
+ rpcUrl?: string;
12
+ network?: 'mainnet' | 'testnet' | 'devnet';
13
+ }
14
+ declare function sui(options: SuiServerOptions): Method.Server<{
15
+ readonly intent: "charge";
16
+ readonly name: "sui";
17
+ readonly schema: {
18
+ readonly credential: {
19
+ readonly payload: zod_mini.ZodMiniObject<{
20
+ digest: zod_mini.ZodMiniString<string>;
21
+ }, zod_v4_core.$strip>;
22
+ };
23
+ readonly request: zod_mini.ZodMiniObject<{
24
+ amount: zod_mini.ZodMiniString<string>;
25
+ currency: zod_mini.ZodMiniString<string>;
26
+ recipient: zod_mini.ZodMiniString<string>;
27
+ }, zod_v4_core.$strip>;
28
+ };
29
+ }, {
30
+ readonly currency: string;
31
+ readonly recipient: string;
32
+ }, undefined>;
33
+
34
+ export { type SuiServerOptions, sui };
@@ -0,0 +1,34 @@
1
+ import * as zod_v4_core from 'zod/v4/core';
2
+ import * as zod_mini from 'zod/mini';
3
+ import { Method } from 'mppx';
4
+ export { S as SUI_USDC_TYPE, s as suiCharge } from './constants-D31h0GdU.js';
5
+
6
+ interface SuiServerOptions {
7
+ currency: string;
8
+ recipient: string;
9
+ /** Number of decimal places for the currency (default: 6, e.g. USDC). */
10
+ decimals?: number;
11
+ rpcUrl?: string;
12
+ network?: 'mainnet' | 'testnet' | 'devnet';
13
+ }
14
+ declare function sui(options: SuiServerOptions): Method.Server<{
15
+ readonly intent: "charge";
16
+ readonly name: "sui";
17
+ readonly schema: {
18
+ readonly credential: {
19
+ readonly payload: zod_mini.ZodMiniObject<{
20
+ digest: zod_mini.ZodMiniString<string>;
21
+ }, zod_v4_core.$strip>;
22
+ };
23
+ readonly request: zod_mini.ZodMiniObject<{
24
+ amount: zod_mini.ZodMiniString<string>;
25
+ currency: zod_mini.ZodMiniString<string>;
26
+ recipient: zod_mini.ZodMiniString<string>;
27
+ }, zod_v4_core.$strip>;
28
+ };
29
+ }, {
30
+ readonly currency: string;
31
+ readonly recipient: string;
32
+ }, undefined>;
33
+
34
+ export { type SuiServerOptions, sui };
package/dist/server.js ADDED
@@ -0,0 +1,99 @@
1
+ import { Method, z, Receipt } from 'mppx';
2
+ import { SuiGrpcClient } from '@mysten/sui/grpc';
3
+ import { normalizeSuiAddress } from '@mysten/sui/utils';
4
+
5
+ // src/server.ts
6
+ var suiCharge = Method.from({
7
+ intent: "charge",
8
+ name: "sui",
9
+ schema: {
10
+ credential: {
11
+ payload: z.object({
12
+ digest: z.string()
13
+ })
14
+ },
15
+ request: z.object({
16
+ amount: z.string(),
17
+ currency: z.string(),
18
+ recipient: z.string()
19
+ })
20
+ }
21
+ });
22
+
23
+ // src/utils.ts
24
+ function parseAmountToRaw(amount, decimals) {
25
+ const [whole = "0", frac = ""] = amount.split(".");
26
+ const paddedFrac = frac.padEnd(decimals, "0").slice(0, decimals);
27
+ return BigInt(whole + paddedFrac);
28
+ }
29
+ async function withRetry(fn, { attempts = 5, baseDelayMs = 1e3 } = {}) {
30
+ let lastError;
31
+ for (let i = 0; i < attempts; i++) {
32
+ try {
33
+ return await fn();
34
+ } catch (err) {
35
+ lastError = err;
36
+ if (i < attempts - 1) {
37
+ await new Promise((r) => setTimeout(r, baseDelayMs * (i + 1)));
38
+ }
39
+ }
40
+ }
41
+ throw lastError;
42
+ }
43
+
44
+ // src/constants.ts
45
+ var SUI_USDC_TYPE = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC";
46
+
47
+ // src/server.ts
48
+ function sui(options) {
49
+ const network = options.network ?? "mainnet";
50
+ const decimals = options.decimals ?? 6;
51
+ const client = new SuiGrpcClient({
52
+ baseUrl: options.rpcUrl ?? `https://fullnode.${network}.sui.io:443`,
53
+ network
54
+ });
55
+ const normalizedRecipient = normalizeSuiAddress(options.recipient);
56
+ return Method.toServer(suiCharge, {
57
+ defaults: {
58
+ currency: options.currency,
59
+ recipient: options.recipient
60
+ },
61
+ async verify({ credential }) {
62
+ const digest = credential.payload.digest;
63
+ const tx = await withRetry(
64
+ () => client.core.getTransaction({ digest, include: { balanceChanges: true } })
65
+ ).catch(() => {
66
+ throw new Error(`Could not find the referenced transaction [${digest}]`);
67
+ });
68
+ const resolved = tx.Transaction ?? tx.FailedTransaction;
69
+ if (!resolved?.status.success) {
70
+ throw new Error("Transaction failed on-chain");
71
+ }
72
+ const payment = resolved.balanceChanges.find(
73
+ (bc) => bc.coinType === options.currency && normalizeSuiAddress(bc.address) === normalizedRecipient && BigInt(bc.amount) > 0n
74
+ );
75
+ if (!payment) {
76
+ throw new Error(
77
+ "Payment not found in transaction balance changes"
78
+ );
79
+ }
80
+ const transferredRaw = BigInt(payment.amount);
81
+ const requestedRaw = parseAmountToRaw(credential.challenge.request.amount, decimals);
82
+ if (transferredRaw < requestedRaw) {
83
+ throw new Error(
84
+ `Transferred ${transferredRaw} < requested ${requestedRaw} (raw units)`
85
+ );
86
+ }
87
+ return Receipt.from({
88
+ method: "sui",
89
+ reference: credential.payload.digest,
90
+ status: "success",
91
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
92
+ });
93
+ }
94
+ });
95
+ }
96
+
97
+ export { SUI_USDC_TYPE, sui, suiCharge };
98
+ //# sourceMappingURL=server.js.map
99
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/method.ts","../src/utils.ts","../src/constants.ts","../src/server.ts"],"names":["Method"],"mappings":"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","file":"server.js","sourcesContent":["import { Method, z } from 'mppx';\n\nexport const suiCharge = Method.from({\n intent: 'charge',\n name: 'sui',\n schema: {\n credential: {\n payload: z.object({\n digest: z.string(),\n }),\n },\n request: z.object({\n amount: z.string(),\n currency: z.string(),\n recipient: z.string(),\n }),\n },\n});\n","/**\n * Parse a string amount to raw bigint units without floating-point math.\n * \"0.01\" with 6 decimals → 10000n\n */\nexport function parseAmountToRaw(amount: string, decimals: number): bigint {\n const [whole = '0', frac = ''] = amount.split('.');\n const paddedFrac = frac.padEnd(decimals, '0').slice(0, decimals);\n return BigInt(whole + paddedFrac);\n}\n\n/**\n * Retry an async function with linear backoff.\n * Throws the last error if all attempts fail.\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n { attempts = 5, baseDelayMs = 1000 }: { attempts?: number; baseDelayMs?: number } = {},\n): Promise<T> {\n let lastError: unknown;\n for (let i = 0; i < attempts; i++) {\n try {\n return await fn();\n } catch (err) {\n lastError = err;\n if (i < attempts - 1) {\n await new Promise((r) => setTimeout(r, baseDelayMs * (i + 1)));\n }\n }\n }\n throw lastError;\n}\n","export const SUI_USDC_TYPE =\n '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC';\n","import { Method, Receipt } from 'mppx';\nimport { SuiGrpcClient } from '@mysten/sui/grpc';\nimport { normalizeSuiAddress } from '@mysten/sui/utils';\nimport { suiCharge } from './method.js';\nimport { parseAmountToRaw, withRetry } from './utils.js';\n\nexport { suiCharge } from './method.js';\nexport { SUI_USDC_TYPE } from './constants.js';\n\nexport interface SuiServerOptions {\n currency: string;\n recipient: string;\n /** Number of decimal places for the currency (default: 6, e.g. USDC). */\n decimals?: number;\n rpcUrl?: string;\n network?: 'mainnet' | 'testnet' | 'devnet';\n}\n\nexport function sui(options: SuiServerOptions) {\n const network = options.network ?? 'mainnet';\n const decimals = options.decimals ?? 6;\n const client = new SuiGrpcClient({\n baseUrl: options.rpcUrl ?? `https://fullnode.${network}.sui.io:443`,\n network,\n });\n\n const normalizedRecipient = normalizeSuiAddress(options.recipient);\n\n return Method.toServer(suiCharge, {\n defaults: {\n currency: options.currency,\n recipient: options.recipient,\n },\n\n async verify({ credential }) {\n const digest = credential.payload.digest;\n\n const tx = await withRetry(\n () => client.core.getTransaction({ digest, include: { balanceChanges: true } }),\n ).catch(() => {\n throw new Error(`Could not find the referenced transaction [${digest}]`);\n });\n\n const resolved = tx.Transaction ?? tx.FailedTransaction;\n if (!resolved?.status.success) {\n throw new Error('Transaction failed on-chain');\n }\n\n const payment = resolved.balanceChanges.find(\n (bc) =>\n bc.coinType === options.currency &&\n normalizeSuiAddress(bc.address) === normalizedRecipient &&\n BigInt(bc.amount) > 0n,\n );\n\n if (!payment) {\n throw new Error(\n 'Payment not found in transaction balance changes',\n );\n }\n\n const transferredRaw = BigInt(payment.amount);\n const requestedRaw = parseAmountToRaw(credential.challenge.request.amount, decimals);\n if (transferredRaw < requestedRaw) {\n throw new Error(\n `Transferred ${transferredRaw} < requested ${requestedRaw} (raw units)`,\n );\n }\n\n return Receipt.from({\n method: 'sui',\n reference: credential.payload.digest,\n status: 'success',\n timestamp: new Date().toISOString(),\n });\n },\n });\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@suimpp/mpp",
3
+ "version": "0.3.0",
4
+ "description": "Sui USDC payment method for the Machine Payments Protocol (MPP)",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./client": {
16
+ "types": "./dist/client.d.ts",
17
+ "import": "./dist/client.js",
18
+ "require": "./dist/client.cjs"
19
+ },
20
+ "./server": {
21
+ "types": "./dist/server.d.ts",
22
+ "import": "./dist/server.js",
23
+ "require": "./dist/server.cjs"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "README.md"
29
+ ],
30
+ "keywords": [
31
+ "mpp",
32
+ "sui",
33
+ "payment",
34
+ "usdc",
35
+ "ai",
36
+ "agent",
37
+ "402",
38
+ "machine-payments",
39
+ "suimpp"
40
+ ],
41
+ "author": "suimpp",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/mission69b/suimpp.git",
45
+ "directory": "packages/mpp"
46
+ },
47
+ "homepage": "https://suimpp.dev",
48
+ "dependencies": {
49
+ "@mysten/sui": "^2",
50
+ "mppx": "^0.4.9",
51
+ "zod": "^4.3.6"
52
+ },
53
+ "devDependencies": {
54
+ "@types/node": "^20",
55
+ "eslint": "^9",
56
+ "tsup": "^8",
57
+ "typescript": "^5",
58
+ "vitest": "^3"
59
+ },
60
+ "license": "MIT",
61
+ "scripts": {
62
+ "build": "tsup",
63
+ "dev": "tsup --watch",
64
+ "test": "vitest run",
65
+ "test:watch": "vitest",
66
+ "typecheck": "tsc --noEmit",
67
+ "lint": "eslint src/",
68
+ "clean": "rm -rf dist"
69
+ }
70
+ }