@stableyard/mppx-stableyard 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +301 -0
- package/demo/.env.example +11 -0
- package/demo/agent.ts +75 -0
- package/demo/server.ts +158 -0
- package/package.json +48 -0
- package/specs/draft-stableyard-charge-00.md +607 -0
- package/src/client.ts +143 -0
- package/src/index.ts +3 -0
- package/src/method.ts +37 -0
- package/src/server.ts +87 -0
- package/src/stableyard-api.ts +275 -0
- package/tsconfig.json +17 -0
package/src/client.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { Credential, Method } from 'mppx'
|
|
2
|
+
import { charge } from './method.js'
|
|
3
|
+
import { StableyardAPI, type StableyardConfig, type Session } from './stableyard-api.js'
|
|
4
|
+
|
|
5
|
+
/** Deposit info returned by Stableyard for the agent to pay */
|
|
6
|
+
export type DepositInfo = {
|
|
7
|
+
/** Deposit type: "direct_transfer" (same-chain) or "gasyard" (cross-chain via gateway) */
|
|
8
|
+
type: string
|
|
9
|
+
/** Address to send funds to (for direct transfer) or gateway address */
|
|
10
|
+
address?: string
|
|
11
|
+
gatewayAddress?: string
|
|
12
|
+
/** Chain ID */
|
|
13
|
+
chainId: number
|
|
14
|
+
/** Token details */
|
|
15
|
+
token: { address: string; symbol: string; decimals: number }
|
|
16
|
+
/** Amount to send */
|
|
17
|
+
amount: { raw: string; formatted: string }
|
|
18
|
+
/** Payment methods — includes gateway calldata for cross-chain */
|
|
19
|
+
methods: Array<{
|
|
20
|
+
type: string
|
|
21
|
+
address?: string
|
|
22
|
+
gatewayAddress?: string
|
|
23
|
+
transactions?: Array<{ type: string; to: string; data: string; value: string; chainId: number }>
|
|
24
|
+
gaslessSupported?: boolean
|
|
25
|
+
gaslessEndpoint?: string
|
|
26
|
+
instructions?: string[]
|
|
27
|
+
}>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Function that executes the on-chain payment.
|
|
32
|
+
* Receives the full deposit info so the agent can choose the best method
|
|
33
|
+
* (direct transfer, gateway, or gasless).
|
|
34
|
+
*/
|
|
35
|
+
export type SendPaymentFn = (deposit: DepositInfo, session: Session) => Promise<string | null>
|
|
36
|
+
|
|
37
|
+
export type ClientConfig = StableyardConfig & {
|
|
38
|
+
/** Preferred chain for payment (e.g., "base", "polygon", "arbitrum") */
|
|
39
|
+
chain?: string
|
|
40
|
+
/** Maximum time to wait for settlement in ms (defaults to 60000) */
|
|
41
|
+
settlementTimeoutMs?: number
|
|
42
|
+
/**
|
|
43
|
+
* Function to execute the on-chain payment.
|
|
44
|
+
* Receives deposit info with address/calldata.
|
|
45
|
+
* Returns tx hash, or null if payment was handled externally (e.g., gasless).
|
|
46
|
+
*
|
|
47
|
+
* If not provided, the client will create the session and wait
|
|
48
|
+
* for external payment (e.g., via checkoutUrl).
|
|
49
|
+
*/
|
|
50
|
+
sendPayment?: SendPaymentFn
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Creates a Stableyard charge method for the client side.
|
|
55
|
+
*
|
|
56
|
+
* When the client encounters a 402 with method="stableyard", it automatically:
|
|
57
|
+
* 1. Creates a Stableyard session with sourceChain → gets deposit address inline
|
|
58
|
+
* 2. Calls sendPayment to execute the transfer (if provided)
|
|
59
|
+
* 3. Submits tx hash for faster detection
|
|
60
|
+
* 4. Polls until settled
|
|
61
|
+
* 5. Returns credential with sessionId as proof
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* import { Mppx } from 'mppx/client'
|
|
66
|
+
* import { stableyard } from 'mppx-stableyard/client'
|
|
67
|
+
*
|
|
68
|
+
* Mppx.create({
|
|
69
|
+
* methods: [
|
|
70
|
+
* stableyard({
|
|
71
|
+
* apiKey: 'sy_secret_...',
|
|
72
|
+
* chain: 'base',
|
|
73
|
+
* sendPayment: async (deposit) => {
|
|
74
|
+
* // Direct transfer: send USDC to deposit address
|
|
75
|
+
* if (deposit.type === 'direct_transfer') {
|
|
76
|
+
* return await sendERC20(deposit.address, deposit.amount.raw)
|
|
77
|
+
* }
|
|
78
|
+
* // Cross-chain: execute gateway transactions
|
|
79
|
+
* for (const tx of deposit.methods[0].transactions) {
|
|
80
|
+
* await walletClient.sendTransaction(tx)
|
|
81
|
+
* }
|
|
82
|
+
* return txHash
|
|
83
|
+
* },
|
|
84
|
+
* }),
|
|
85
|
+
* ],
|
|
86
|
+
* })
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export function stableyard(config: ClientConfig) {
|
|
90
|
+
const api = new StableyardAPI({
|
|
91
|
+
apiKey: config.apiKey,
|
|
92
|
+
baseUrl: config.baseUrl,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const chain = config.chain ?? 'base'
|
|
96
|
+
const settlementTimeoutMs = config.settlementTimeoutMs ?? 60_000
|
|
97
|
+
|
|
98
|
+
return Method.toClient(charge, {
|
|
99
|
+
async createCredential({ challenge }) {
|
|
100
|
+
const { amount, currency, destination } = challenge.request
|
|
101
|
+
|
|
102
|
+
// 1. Create session with sourceChain → deposit address inline
|
|
103
|
+
const session = await api.createSession({
|
|
104
|
+
amount: Number(amount),
|
|
105
|
+
destination,
|
|
106
|
+
currency: (currency === 'USDC' || currency === 'USDT') ? currency : 'USDC',
|
|
107
|
+
sourceChain: chain,
|
|
108
|
+
resource: `${challenge.realm}/${challenge.intent}`,
|
|
109
|
+
metadata: {
|
|
110
|
+
mpp_challenge_id: challenge.id,
|
|
111
|
+
mpp_method: 'stableyard',
|
|
112
|
+
},
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
// 2. Execute payment if sendPayment provided and deposit info available
|
|
116
|
+
if (config.sendPayment && session.deposit) {
|
|
117
|
+
const txHash = await config.sendPayment(session.deposit as unknown as DepositInfo, session)
|
|
118
|
+
|
|
119
|
+
// 3. Submit tx hash for faster detection
|
|
120
|
+
if (txHash) {
|
|
121
|
+
await api.submitTx(session.id, txHash, chain).catch(() => {
|
|
122
|
+
// Non-fatal — Stableyard may detect independently
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 4. Wait for settlement
|
|
128
|
+
await api.waitForSettlement(session.id, settlementTimeoutMs)
|
|
129
|
+
|
|
130
|
+
// 5. Return credential with session ID as proof
|
|
131
|
+
return Credential.serialize(
|
|
132
|
+
Credential.from({
|
|
133
|
+
challenge,
|
|
134
|
+
payload: {
|
|
135
|
+
sessionId: session.id,
|
|
136
|
+
},
|
|
137
|
+
}),
|
|
138
|
+
)
|
|
139
|
+
},
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export { charge } from './method.js'
|
package/src/index.ts
ADDED
package/src/method.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Method, z } from 'mppx'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stableyard charge method definition for MPP.
|
|
5
|
+
*
|
|
6
|
+
* Enables cross-chain payments via Stableyard's settlement network.
|
|
7
|
+
* Agent pays from any supported chain → Stableyard routes → merchant
|
|
8
|
+
* receives on their preferred chain/token or fiat to bank.
|
|
9
|
+
*/
|
|
10
|
+
export const charge = Method.from({
|
|
11
|
+
name: 'stableyard',
|
|
12
|
+
intent: 'charge',
|
|
13
|
+
schema: {
|
|
14
|
+
credential: {
|
|
15
|
+
payload: z.object({
|
|
16
|
+
/** Stableyard session ID proving payment */
|
|
17
|
+
sessionId: z.string(),
|
|
18
|
+
/** Transaction hash (optional, for faster detection) */
|
|
19
|
+
txHash: z.optional(z.string()),
|
|
20
|
+
}),
|
|
21
|
+
},
|
|
22
|
+
request: z.object({
|
|
23
|
+
/** Amount in smallest unit (e.g., "1000000" for $1 USDC with 6 decimals) */
|
|
24
|
+
amount: z.string(),
|
|
25
|
+
/** Currency identifier (e.g., "USDC", "USDT") */
|
|
26
|
+
currency: z.string(),
|
|
27
|
+
/** Number of decimal places for the currency */
|
|
28
|
+
decimals: z.number(),
|
|
29
|
+
/** Stableyard destination — username (merchant@stableyard) or wallet address */
|
|
30
|
+
destination: z.string(),
|
|
31
|
+
/** Human-readable description */
|
|
32
|
+
description: z.optional(z.string()),
|
|
33
|
+
/** External reference ID for merchant reconciliation */
|
|
34
|
+
externalId: z.optional(z.string()),
|
|
35
|
+
}),
|
|
36
|
+
},
|
|
37
|
+
})
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Method } from 'mppx'
|
|
2
|
+
import { charge } from './method.js'
|
|
3
|
+
import { StableyardAPI, type StableyardConfig } from './stableyard-api.js'
|
|
4
|
+
|
|
5
|
+
export type ServerConfig = StableyardConfig & {
|
|
6
|
+
/** Stableyard destination for receiving payments (e.g., "merchant@stableyard" or wallet address) */
|
|
7
|
+
destination: string
|
|
8
|
+
/** Default currency (defaults to "USDC") */
|
|
9
|
+
currency?: string
|
|
10
|
+
/** Number of decimal places (defaults to 6 for USDC) */
|
|
11
|
+
decimals?: number
|
|
12
|
+
/** Maximum time to wait for settlement verification in ms (defaults to 30000) */
|
|
13
|
+
verifyTimeoutMs?: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates a Stableyard charge method for the server side.
|
|
18
|
+
*
|
|
19
|
+
* Accepts payments from any chain supported by Stableyard (Base, Arbitrum,
|
|
20
|
+
* Polygon, BNB, Ethereum, Solana, Movement) and settles to the merchant's
|
|
21
|
+
* preferred chain/token/fiat.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { Mppx } from 'mppx/server'
|
|
26
|
+
* import { stableyard } from 'mppx-stableyard/server'
|
|
27
|
+
*
|
|
28
|
+
* const mppx = Mppx.create({
|
|
29
|
+
* methods: [
|
|
30
|
+
* stableyard({
|
|
31
|
+
* apiKey: 'sy_secret_...',
|
|
32
|
+
* destination: 'merchant@stableyard',
|
|
33
|
+
* }),
|
|
34
|
+
* ],
|
|
35
|
+
* })
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export function stableyard(config: ServerConfig) {
|
|
39
|
+
const api = new StableyardAPI({
|
|
40
|
+
apiKey: config.apiKey,
|
|
41
|
+
baseUrl: config.baseUrl,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const currency = config.currency ?? 'USDC'
|
|
45
|
+
const decimals = config.decimals ?? 6
|
|
46
|
+
const verifyTimeoutMs = config.verifyTimeoutMs ?? 30_000
|
|
47
|
+
|
|
48
|
+
return Method.toServer(charge, {
|
|
49
|
+
defaults: {
|
|
50
|
+
currency,
|
|
51
|
+
decimals,
|
|
52
|
+
destination: config.destination,
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
async verify({ credential, request }) {
|
|
56
|
+
const { sessionId, txHash } = credential.payload
|
|
57
|
+
|
|
58
|
+
// Submit tx hash for faster detection if provided
|
|
59
|
+
if (txHash) {
|
|
60
|
+
await api.submitTx(sessionId, txHash, 'auto').catch(() => {
|
|
61
|
+
// Non-fatal — Stableyard may detect it independently
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Verify the session payment
|
|
66
|
+
const verification = await api.verifySession(sessionId)
|
|
67
|
+
|
|
68
|
+
if (!verification.verified) {
|
|
69
|
+
// If not yet verified, poll for settlement
|
|
70
|
+
try {
|
|
71
|
+
await api.waitForSettlement(sessionId, verifyTimeoutMs)
|
|
72
|
+
} catch {
|
|
73
|
+
throw new Error(`Payment verification failed for session ${sessionId}`)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
method: 'stableyard' as const,
|
|
79
|
+
status: 'success' as const,
|
|
80
|
+
reference: sessionId,
|
|
81
|
+
timestamp: new Date().toISOString(),
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { charge } from './method.js'
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stableyard API client — thin wrapper around api.stableyard.fi/v2
|
|
3
|
+
*
|
|
4
|
+
* Handles session creation, verification, payment, and status polling.
|
|
5
|
+
* Based on OpenAPI spec at https://api.stableyard.fi/sdk.json
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const PRODUCTION_URL = 'https://api.stableyard.fi/v2'
|
|
9
|
+
const SANDBOX_URL = 'https://api-staging.stableyard.fi/v2'
|
|
10
|
+
|
|
11
|
+
export type StableyardConfig = {
|
|
12
|
+
/** Stableyard API key (sy_secret_* for server, sy_pub_* for client) */
|
|
13
|
+
apiKey: string
|
|
14
|
+
/** Base URL override. Auto-detected from key prefix (sy_test_* → sandbox) */
|
|
15
|
+
baseUrl?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type CreateSessionParams = {
|
|
19
|
+
/** Amount in smallest unit (6 decimals). 10000000 = $10.00 USDC */
|
|
20
|
+
amount: number
|
|
21
|
+
/** Destination — username@stableyard or wallet address */
|
|
22
|
+
destination: string
|
|
23
|
+
/** Currency: "USDC" or "USDT". Defaults to "USDC" */
|
|
24
|
+
currency?: 'USDC' | 'USDT'
|
|
25
|
+
/** Human-readable description (max 500 chars) */
|
|
26
|
+
description?: string
|
|
27
|
+
/** Metadata (max 20 keys, 500 chars each) */
|
|
28
|
+
metadata?: Record<string, string>
|
|
29
|
+
/** Redirect URL on success */
|
|
30
|
+
successUrl?: string
|
|
31
|
+
/** API path for x402/paywall use case */
|
|
32
|
+
resource?: string
|
|
33
|
+
/** Restrict payer chains. Default: all supported */
|
|
34
|
+
acceptedChains?: string[]
|
|
35
|
+
/** Generate QR codes for POS */
|
|
36
|
+
qrFormats?: ('universal' | 'solana' | 'evm')[]
|
|
37
|
+
/** Revenue splits (basis points, must total 10000) */
|
|
38
|
+
splits?: Array<{ destination: string; percentage: number }>
|
|
39
|
+
/** TTL in seconds. Default: 900 */
|
|
40
|
+
expiresIn?: number
|
|
41
|
+
/** Source chain — if provided, calls Intent Engine inline and returns deposit address */
|
|
42
|
+
sourceChain?: string
|
|
43
|
+
/** Source token — defaults to session currency */
|
|
44
|
+
sourceToken?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type Session = {
|
|
48
|
+
object: 'session'
|
|
49
|
+
id: string
|
|
50
|
+
type: 'checkout' | 'deposit' | 'transfer'
|
|
51
|
+
status: 'open' | 'pending' | 'routing' | 'settled' | 'failed' | 'expired' | 'refunded'
|
|
52
|
+
livemode: boolean
|
|
53
|
+
amount: number
|
|
54
|
+
currency: 'USDC' | 'USDT'
|
|
55
|
+
amountFormatted: string
|
|
56
|
+
destination: {
|
|
57
|
+
accountId: string
|
|
58
|
+
paymentAddress: string | null
|
|
59
|
+
wallet: string
|
|
60
|
+
chain: string
|
|
61
|
+
token: string
|
|
62
|
+
}
|
|
63
|
+
source: {
|
|
64
|
+
wallet: string
|
|
65
|
+
chain: string
|
|
66
|
+
token: string
|
|
67
|
+
} | null
|
|
68
|
+
fees: {
|
|
69
|
+
total: number
|
|
70
|
+
totalFormatted: string
|
|
71
|
+
stableyard: number
|
|
72
|
+
partner: number
|
|
73
|
+
}
|
|
74
|
+
checkoutUrl: string | null
|
|
75
|
+
clientSecret: string | null
|
|
76
|
+
paymentMethods: Array<{
|
|
77
|
+
type: 'wallet_direct' | 'vault_balance'
|
|
78
|
+
depositAddress: string | null
|
|
79
|
+
chain?: string
|
|
80
|
+
}> | null
|
|
81
|
+
/** Deposit info — populated when sourceChain is provided in creation */
|
|
82
|
+
deposit?: {
|
|
83
|
+
/** "direct_transfer" (same-chain) or "gasyard" (cross-chain via gateway) */
|
|
84
|
+
type: string
|
|
85
|
+
/** Deposit address (for direct transfer) */
|
|
86
|
+
address?: string
|
|
87
|
+
/** Gateway contract address (for cross-chain) */
|
|
88
|
+
gatewayAddress?: string
|
|
89
|
+
/** Chain ID for payment */
|
|
90
|
+
chainId: number
|
|
91
|
+
/** Token details */
|
|
92
|
+
token: { address: string; symbol: string; decimals: number }
|
|
93
|
+
/** Amount to send */
|
|
94
|
+
amount: { raw: string; formatted: string }
|
|
95
|
+
/** Payment methods with calldata */
|
|
96
|
+
methods: Array<{
|
|
97
|
+
type: string
|
|
98
|
+
address?: string
|
|
99
|
+
gatewayAddress?: string
|
|
100
|
+
transactions?: Array<{ type: string; to: string; data: string; value: string; chainId: number }>
|
|
101
|
+
gaslessSupported?: boolean
|
|
102
|
+
gaslessEndpoint?: string
|
|
103
|
+
instructions?: string[]
|
|
104
|
+
}>
|
|
105
|
+
} | null
|
|
106
|
+
metadata: Record<string, string>
|
|
107
|
+
refundedAmount: number
|
|
108
|
+
createdAt: number
|
|
109
|
+
updatedAt: number
|
|
110
|
+
expiresAt: number
|
|
111
|
+
settledAt: number | null
|
|
112
|
+
requestId: string
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export type QuoteResponse = {
|
|
116
|
+
fees: {
|
|
117
|
+
total: number
|
|
118
|
+
stableyard: number
|
|
119
|
+
}
|
|
120
|
+
merchantReceives: number
|
|
121
|
+
route: {
|
|
122
|
+
sourceChain: string
|
|
123
|
+
destChain: string
|
|
124
|
+
estimatedSeconds: number
|
|
125
|
+
}
|
|
126
|
+
intentOrderId?: string
|
|
127
|
+
deposit?: {
|
|
128
|
+
address: string
|
|
129
|
+
gatewayAddress: string
|
|
130
|
+
methods: Array<{
|
|
131
|
+
type: 'gateway' | 'deposit_address'
|
|
132
|
+
transactions?: Array<{ type: string; to: string; data: string }>
|
|
133
|
+
address?: string
|
|
134
|
+
}>
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export type VerifyResponse = {
|
|
139
|
+
verified: boolean
|
|
140
|
+
status: string
|
|
141
|
+
sessionId: string
|
|
142
|
+
[key: string]: unknown
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function detectBaseUrl(apiKey: string): string {
|
|
146
|
+
if (apiKey.startsWith('sy_test_')) return SANDBOX_URL
|
|
147
|
+
return PRODUCTION_URL
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export class StableyardAPI {
|
|
151
|
+
private readonly apiKey: string
|
|
152
|
+
private readonly baseUrl: string
|
|
153
|
+
|
|
154
|
+
constructor(config: StableyardConfig) {
|
|
155
|
+
this.apiKey = config.apiKey
|
|
156
|
+
this.baseUrl = config.baseUrl ?? detectBaseUrl(config.apiKey)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private async request<T>(path: string, options: RequestInit = {}): Promise<T> {
|
|
160
|
+
const url = `${this.baseUrl}${path}`
|
|
161
|
+
const res = await fetch(url, {
|
|
162
|
+
...options,
|
|
163
|
+
headers: {
|
|
164
|
+
'Content-Type': 'application/json',
|
|
165
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
166
|
+
...options.headers,
|
|
167
|
+
},
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
if (!res.ok) {
|
|
171
|
+
const body = await res.text().catch(() => '')
|
|
172
|
+
throw new Error(`Stableyard API error ${res.status} on ${path}: ${body}`)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return res.json() as Promise<T>
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Create a payment session */
|
|
179
|
+
async createSession(params: CreateSessionParams): Promise<Session> {
|
|
180
|
+
return this.request<Session>('/sessions', {
|
|
181
|
+
method: 'POST',
|
|
182
|
+
body: JSON.stringify({
|
|
183
|
+
amount: params.amount,
|
|
184
|
+
destination: params.destination,
|
|
185
|
+
currency: params.currency,
|
|
186
|
+
description: params.description,
|
|
187
|
+
metadata: params.metadata,
|
|
188
|
+
successUrl: params.successUrl,
|
|
189
|
+
resource: params.resource,
|
|
190
|
+
acceptedChains: params.acceptedChains,
|
|
191
|
+
qrFormats: params.qrFormats,
|
|
192
|
+
splits: params.splits,
|
|
193
|
+
expiresIn: params.expiresIn,
|
|
194
|
+
sourceChain: params.sourceChain,
|
|
195
|
+
sourceToken: params.sourceToken,
|
|
196
|
+
}),
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Get session details */
|
|
201
|
+
async getSession(sessionId: string): Promise<Session> {
|
|
202
|
+
return this.request<Session>(`/sessions/${sessionId}`)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Get a quote for a specific source chain (preview without commit) */
|
|
206
|
+
async getQuote(sessionId: string, sourceChain: string, commit?: boolean): Promise<QuoteResponse> {
|
|
207
|
+
return this.request<QuoteResponse>(`/sessions/${sessionId}/quote`, {
|
|
208
|
+
method: 'POST',
|
|
209
|
+
body: JSON.stringify({ sourceChain, commit }),
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Verify a payment session (for x402/MPP use case) */
|
|
214
|
+
async verifySession(sessionId: string): Promise<VerifyResponse> {
|
|
215
|
+
return this.request<VerifyResponse>(`/sessions/${sessionId}/verify`, {
|
|
216
|
+
method: 'POST',
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Submit transaction hash for faster detection */
|
|
221
|
+
async submitTx(sessionId: string, txHash: string, chain: string): Promise<{ status: string }> {
|
|
222
|
+
return this.request(`/sessions/${sessionId}/submit-tx`, {
|
|
223
|
+
method: 'POST',
|
|
224
|
+
body: JSON.stringify({ txHash, chain }),
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Pay a session from vault (gasless EIP-712) */
|
|
229
|
+
async payFromVault(
|
|
230
|
+
sessionId: string,
|
|
231
|
+
params: { method: 'vault_balance'; signedMessage: string; signerAddress: string },
|
|
232
|
+
): Promise<{ status: string }> {
|
|
233
|
+
return this.request(`/sessions/${sessionId}/pay`, {
|
|
234
|
+
method: 'POST',
|
|
235
|
+
body: JSON.stringify(params),
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Cancel a session */
|
|
240
|
+
async cancelSession(sessionId: string): Promise<void> {
|
|
241
|
+
await this.request(`/sessions/${sessionId}/cancel`, { method: 'POST' })
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Resolve a wallet/username/ID to account details */
|
|
245
|
+
async resolveAccount(query: string): Promise<Record<string, unknown>> {
|
|
246
|
+
return this.request(`/accounts/resolve?q=${encodeURIComponent(query)}`)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Check multi-chain portfolio balances (no auth required) */
|
|
250
|
+
async getPortfolio(address: string): Promise<Record<string, unknown>> {
|
|
251
|
+
return this.request(`/network/portfolio?address=${encodeURIComponent(address)}`)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Poll session until settled or timeout */
|
|
255
|
+
async waitForSettlement(
|
|
256
|
+
sessionId: string,
|
|
257
|
+
timeoutMs: number = 60_000,
|
|
258
|
+
pollIntervalMs: number = 2_000,
|
|
259
|
+
): Promise<Session> {
|
|
260
|
+
const deadline = Date.now() + timeoutMs
|
|
261
|
+
|
|
262
|
+
while (Date.now() < deadline) {
|
|
263
|
+
const session = await this.getSession(sessionId)
|
|
264
|
+
|
|
265
|
+
if (session.status === 'settled') return session
|
|
266
|
+
if (session.status === 'failed' || session.status === 'expired' || session.status === 'refunded') {
|
|
267
|
+
throw new Error(`Session ${sessionId} ${session.status}`)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
await new Promise(resolve => setTimeout(resolve, pollIntervalMs))
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
throw new Error(`Session ${sessionId} timed out waiting for settlement`)
|
|
274
|
+
}
|
|
275
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"rootDir": "src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src"],
|
|
16
|
+
"exclude": ["node_modules", "dist", "demo"]
|
|
17
|
+
}
|