@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 +21 -0
- package/README.md +210 -0
- package/dist/client.cjs +79 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +36 -0
- package/dist/client.d.ts +36 -0
- package/dist/client.js +75 -0
- package/dist/client.js.map +1 -0
- package/dist/constants-D31h0GdU.d.cts +22 -0
- package/dist/constants-D31h0GdU.d.ts +22 -0
- package/dist/index.cjs +146 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +25 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +139 -0
- package/dist/index.js.map +1 -0
- package/dist/server.cjs +103 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +34 -0
- package/dist/server.d.ts +34 -0
- package/dist/server.js +99 -0
- package/dist/server.js.map +1 -0
- package/package.json +70 -0
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
|
+
[](https://www.npmjs.com/package/@suimpp/mpp)
|
|
6
|
+
[](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)
|
package/dist/client.cjs
ADDED
|
@@ -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 };
|
package/dist/client.d.ts
ADDED
|
@@ -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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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 };
|
package/dist/index.d.ts
ADDED
|
@@ -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"]}
|
package/dist/server.cjs
ADDED
|
@@ -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 };
|
package/dist/server.d.ts
ADDED
|
@@ -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
|
+
}
|