-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathMethods.ts
More file actions
97 lines (93 loc) · 3.37 KB
/
Methods.ts
File metadata and controls
97 lines (93 loc) · 3.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import { Method } from 'mppx'
import { z } from 'zod/mini'
/**
* Stellar charge intent for one-time SAC token transfers.
*
* Supports two credential flows:
* - `type: "transaction"` — **server-broadcast** (pull mode):
* Client signs a Soroban SAC `transfer` invocation and sends the
* serialised XDR as `payload.transaction`. The server broadcasts it.
* - `type: "hash"` — **client-broadcast** (push mode):
* Client broadcasts itself and sends the transaction hash.
* The server looks it up on-chain for verification.
*
* @see https://stellar.org
*/
export const charge = Method.from({
name: 'stellar',
intent: 'charge',
schema: {
credential: {
payload: z.discriminatedUnion('type', [
/** Push mode: client broadcasts and sends the tx hash. */
z.object({ hash: z.string(), type: z.literal('hash') }),
/** Pull mode: client sends signed XDR as `payload.transaction`, server broadcasts. */
z.object({ transaction: z.string(), type: z.literal('transaction') }),
]),
},
request: z.object({
/** Payment amount in base units (stroops). */
amount: z.string(),
/** SAC contract address (C...) for the token to transfer. */
currency: z.string(),
/** Recipient Stellar public key (G...) or contract address (C...). */
recipient: z.string(),
/** Optional human-readable description. */
description: z.optional(z.string()),
/** Merchant-provided reconciliation ID (e.g. order ID, invoice number). */
externalId: z.optional(z.string()),
/** Method-specific details injected by the server. */
methodDetails: z.optional(
z.object({
/** Server-generated unique tracking ID. */
reference: z.optional(z.string()),
/** Stellar network identifier ("public" | "testnet"). */
network: z.optional(z.string()),
/** Optional memo text to attach to the transaction. */
memo: z.optional(z.string()),
/** Whether the server will sponsor transaction fees. */
feePayer: z.optional(z.boolean()),
}),
),
}),
},
})
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/**
* Convert a human-readable amount to base units (stroops).
*
* @example
* ```ts
* toBaseUnits('0.01', 7) // '100000'
* toBaseUnits('1', 7) // '10000000'
* ```
*/
export function toBaseUnits(amount: string, decimals: number): string {
if (amount.startsWith('-')) {
return '-' + toBaseUnits(amount.slice(1), decimals)
}
const [whole = '0', frac = ''] = amount.split('.')
if (decimals === 0) return BigInt(whole).toString()
const paddedFrac = frac.padEnd(decimals, '0').slice(0, decimals)
return (BigInt(whole) * 10n ** BigInt(decimals) + BigInt(paddedFrac)).toString()
}
/**
* Convert base units (stroops) back to a human-readable amount.
*
* @example
* ```ts
* fromBaseUnits('100000', 7) // '0.0100000'
* ```
*/
export function fromBaseUnits(baseUnits: string, decimals: number): string {
const bi = BigInt(baseUnits)
if (bi < 0n) {
return '-' + fromBaseUnits((-bi).toString(), decimals)
}
const divisor = 10n ** BigInt(decimals)
const whole = (bi / divisor).toString()
const remainder = (bi % divisor).toString().padStart(decimals, '0')
return `${whole}.${remainder}`
}