@t402/stacks 2.4.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.
Files changed (40) hide show
  1. package/README.md +178 -0
  2. package/dist/exact-direct/client/index.cjs +167 -0
  3. package/dist/exact-direct/client/index.cjs.map +1 -0
  4. package/dist/exact-direct/client/index.d.cts +39 -0
  5. package/dist/exact-direct/client/index.d.ts +39 -0
  6. package/dist/exact-direct/client/index.mjs +139 -0
  7. package/dist/exact-direct/client/index.mjs.map +1 -0
  8. package/dist/exact-direct/facilitator/index.cjs +395 -0
  9. package/dist/exact-direct/facilitator/index.cjs.map +1 -0
  10. package/dist/exact-direct/facilitator/index.d.cts +55 -0
  11. package/dist/exact-direct/facilitator/index.d.ts +55 -0
  12. package/dist/exact-direct/facilitator/index.mjs +367 -0
  13. package/dist/exact-direct/facilitator/index.mjs.map +1 -0
  14. package/dist/exact-direct/server/index.cjs +247 -0
  15. package/dist/exact-direct/server/index.cjs.map +1 -0
  16. package/dist/exact-direct/server/index.d.cts +109 -0
  17. package/dist/exact-direct/server/index.d.ts +109 -0
  18. package/dist/exact-direct/server/index.mjs +218 -0
  19. package/dist/exact-direct/server/index.mjs.map +1 -0
  20. package/dist/index.cjs +261 -0
  21. package/dist/index.cjs.map +1 -0
  22. package/dist/index.d.cts +126 -0
  23. package/dist/index.d.ts +126 -0
  24. package/dist/index.mjs +212 -0
  25. package/dist/index.mjs.map +1 -0
  26. package/dist/types-Bxzo3eQ1.d.cts +172 -0
  27. package/dist/types-Bxzo3eQ1.d.ts +172 -0
  28. package/package.json +102 -0
  29. package/src/constants.ts +66 -0
  30. package/src/exact-direct/client/index.ts +5 -0
  31. package/src/exact-direct/client/scheme.ts +115 -0
  32. package/src/exact-direct/facilitator/index.ts +4 -0
  33. package/src/exact-direct/facilitator/scheme.ts +308 -0
  34. package/src/exact-direct/server/index.ts +9 -0
  35. package/src/exact-direct/server/register.ts +57 -0
  36. package/src/exact-direct/server/scheme.ts +216 -0
  37. package/src/index.ts +78 -0
  38. package/src/tokens.ts +96 -0
  39. package/src/types.ts +184 -0
  40. package/src/utils.ts +198 -0
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # @t402/stacks
2
+
3
+ Stacks (Bitcoin L2) implementation of the t402 payment protocol using the **exact-direct** payment scheme with SIP-010 fungible token transfers.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @t402/stacks
9
+ # or
10
+ pnpm add @t402/stacks
11
+ ```
12
+
13
+ ## Overview
14
+
15
+ This package provides support for sUSDC payments on Stacks using the exact-direct scheme. The client executes a `transfer` call on a SIP-010 token contract directly on-chain, then provides the transaction ID as proof of payment.
16
+
17
+ Three main components:
18
+
19
+ - **Client** - For applications that make payments (have Stacks wallets)
20
+ - **Facilitator** - For payment processors that verify transactions via the Hiro API
21
+ - **Server** - For resource servers that accept payments and build payment requirements
22
+
23
+ ## Supported Networks
24
+
25
+ | Network | CAIP-2 Identifier | sUSDC Contract | Status |
26
+ |---------|-------------------|----------------|--------|
27
+ | Stacks Mainnet | `stacks:1` | `SP3Y2ZSH8P7D50B0VBTSX11S7XSG24M1VB9YFQA4K.token-susdc` | Production |
28
+ | Stacks Testnet | `stacks:2147483648` | `ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.token-susdc` | Testnet |
29
+
30
+ ## Package Exports
31
+
32
+ ### Main Package (`@t402/stacks`)
33
+
34
+ **Constants:**
35
+
36
+ - `STACKS_CAIP2_NAMESPACE` - CAIP-2 namespace (`stacks`)
37
+ - `STACKS_MAINNET_CAIP2` - Mainnet identifier (`stacks:1`)
38
+ - `STACKS_TESTNET_CAIP2` - Testnet identifier (`stacks:2147483648`)
39
+ - `STACKS_NETWORKS` - Array of supported networks
40
+ - `SCHEME_EXACT_DIRECT` - Scheme identifier
41
+ - `DEFAULT_MAINNET_API` - Mainnet Hiro API endpoint
42
+ - `DEFAULT_TESTNET_API` - Testnet Hiro API endpoint
43
+
44
+ **Tokens:**
45
+
46
+ - `TOKEN_REGISTRY` - Token configurations by network
47
+ - `getTokenConfig(network, symbol)` - Get token by symbol
48
+ - `getDefaultToken(network)` - Get default token
49
+ - `getContractAddress(network, symbol)` - Get contract address
50
+
51
+ **Utilities:**
52
+
53
+ - `isValidPrincipal(principal)` - Validate Stacks principal format
54
+ - `isValidTxId(txId)` - Validate transaction ID format
55
+ - `isStacksNetwork(network)` - Check if network is supported
56
+ - `getNetworkConfig(network)` - Get network configuration
57
+ - `formatAmount(amount, decimals)` - Format for display
58
+ - `parseAmount(amount, decimals)` - Convert to atomic units
59
+ - `extractTokenTransfer(events)` - Extract transfer from transaction events
60
+ - `comparePrincipals(a, b)` - Compare two Stacks principals
61
+
62
+ ### Client (`@t402/stacks/exact-direct/client`)
63
+
64
+ ```typescript
65
+ import { createExactDirectStacksClient } from "@t402/stacks/exact-direct/client";
66
+
67
+ const client = createExactDirectStacksClient({
68
+ signer: myStacksSigner,
69
+ });
70
+ ```
71
+
72
+ ### Server (`@t402/stacks/exact-direct/server`)
73
+
74
+ ```typescript
75
+ import { registerExactDirectStacksServer } from "@t402/stacks/exact-direct/server";
76
+
77
+ registerExactDirectStacksServer(server);
78
+ ```
79
+
80
+ ### Facilitator (`@t402/stacks/exact-direct/facilitator`)
81
+
82
+ ```typescript
83
+ import { createExactDirectStacksFacilitator } from "@t402/stacks/exact-direct/facilitator";
84
+
85
+ const facilitator = createExactDirectStacksFacilitator(signer);
86
+ ```
87
+
88
+ ## Payment Flow
89
+
90
+ 1. **Client** requests protected resource
91
+ 2. **Server** responds with 402 + payment requirements (network, amount, payTo)
92
+ 3. **Client** calls SIP-010 `transfer` on the sUSDC contract
93
+ 4. **Client** submits transaction ID as payment proof
94
+ 5. **Facilitator** queries Hiro API to verify the transaction
95
+ 6. **Facilitator** confirms payment matches requirements
96
+
97
+ ## Payload Structure
98
+
99
+ ```typescript
100
+ interface ExactDirectStacksPayload {
101
+ txId: string; // Transaction ID (0x-prefixed hex)
102
+ from: string; // Sender principal
103
+ to: string; // Recipient principal
104
+ amount: string; // Amount in atomic units (6 decimals)
105
+ contractAddress: string; // SIP-010 contract address
106
+ }
107
+ ```
108
+
109
+ ## Principal Format
110
+
111
+ Stacks uses principal-based addressing:
112
+
113
+ - **Standard principals**: `SP` (mainnet) or `ST` (testnet) prefix + 33-41 alphanumeric characters
114
+ - **Contract principals**: `{standard-principal}.{contract-name}`
115
+
116
+ Examples:
117
+
118
+ ```
119
+ SP3Y2ZSH8P7D50B0VBTSX11S7XSG24M1VB9YFQA4K
120
+ SP3Y2ZSH8P7D50B0VBTSX11S7XSG24M1VB9YFQA4K.token-susdc
121
+ ```
122
+
123
+ ## SIP-010 Token Standard
124
+
125
+ This package uses the SIP-010 fungible token standard on Stacks:
126
+
127
+ - `transfer(amount, sender, recipient, memo?)` - Transfer tokens
128
+ - `get-balance(owner)` - Query balance
129
+ - Tokens have 6 decimal places (sUSDC)
130
+
131
+ ## CAIP-19 Asset Identifiers
132
+
133
+ Assets use CAIP-19 format:
134
+
135
+ ```
136
+ stacks:1/sip010:SP3Y2ZSH8P7D50B0VBTSX11S7XSG24M1VB9YFQA4K.token-susdc
137
+ ```
138
+
139
+ ## Transaction Verification
140
+
141
+ The facilitator verifies payments by:
142
+
143
+ - Querying the Hiro API for transaction details
144
+ - Checking transaction status is `"success"`
145
+ - Extracting token transfer from events or post conditions
146
+ - Verifying: contract address, recipient, and amount (>= required)
147
+ - Replay prevention via transaction ID caching (24-hour default)
148
+ - Configurable max transaction age (1 hour default)
149
+
150
+ ## Development
151
+
152
+ ```bash
153
+ # Build
154
+ pnpm build
155
+
156
+ # Test
157
+ pnpm test
158
+
159
+ # Test with coverage
160
+ pnpm test:coverage
161
+
162
+ # Lint
163
+ pnpm lint
164
+ ```
165
+
166
+ ## Related Packages
167
+
168
+ - `@t402/core` - Core protocol types and client
169
+ - `@t402/fetch` - HTTP wrapper with automatic payment handling
170
+ - `@t402/evm` - EVM implementation
171
+ - `@t402/svm` - Solana implementation
172
+ - `@t402/ton` - TON implementation
173
+ - `@t402/tron` - TRON implementation
174
+ - `@t402/near` - NEAR Protocol implementation
175
+ - `@t402/aptos` - Aptos implementation
176
+ - `@t402/tezos` - Tezos implementation
177
+ - `@t402/polkadot` - Polkadot Asset Hub implementation
178
+ - `@t402/cosmos` - Cosmos (Noble) implementation
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/exact-direct/client/index.ts
21
+ var client_exports = {};
22
+ __export(client_exports, {
23
+ ExactDirectStacksClient: () => ExactDirectStacksClient,
24
+ createExactDirectStacksClient: () => createExactDirectStacksClient
25
+ });
26
+ module.exports = __toCommonJS(client_exports);
27
+
28
+ // src/constants.ts
29
+ var STACKS_CAIP2_NAMESPACE = "stacks";
30
+ var STACKS_MAINNET_CAIP2 = "stacks:1";
31
+ var STACKS_TESTNET_CAIP2 = "stacks:2147483648";
32
+ var SCHEME_EXACT_DIRECT = "exact-direct";
33
+ var DEFAULT_MAINNET_API = "https://api.mainnet.hiro.so";
34
+ var DEFAULT_TESTNET_API = "https://api.testnet.hiro.so";
35
+ var STACKS_NETWORKS = {
36
+ [STACKS_MAINNET_CAIP2]: {
37
+ name: "Stacks Mainnet",
38
+ caip2: STACKS_MAINNET_CAIP2,
39
+ apiUrl: DEFAULT_MAINNET_API,
40
+ chainId: 1,
41
+ addressPrefix: "SP",
42
+ isTestnet: false
43
+ },
44
+ [STACKS_TESTNET_CAIP2]: {
45
+ name: "Stacks Testnet",
46
+ caip2: STACKS_TESTNET_CAIP2,
47
+ apiUrl: DEFAULT_TESTNET_API,
48
+ chainId: 2147483648,
49
+ addressPrefix: "ST",
50
+ isTestnet: true
51
+ }
52
+ };
53
+
54
+ // src/tokens.ts
55
+ var SUSDC_MAINNET = {
56
+ contractAddress: "SP3Y2ZSH8P7D50B0VBTSX11S7XSG24M1VB9YFQA4K.token-susdc",
57
+ symbol: "sUSDC",
58
+ name: "Stacks USDC",
59
+ decimals: 6,
60
+ issuer: "Stacks"
61
+ };
62
+ var SUSDC_TESTNET = {
63
+ contractAddress: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.token-susdc",
64
+ symbol: "sUSDC",
65
+ name: "Test Stacks USDC",
66
+ decimals: 6
67
+ };
68
+ var TOKEN_REGISTRY = {
69
+ [STACKS_MAINNET_CAIP2]: {
70
+ sUSDC: SUSDC_MAINNET
71
+ },
72
+ [STACKS_TESTNET_CAIP2]: {
73
+ sUSDC: SUSDC_TESTNET
74
+ }
75
+ };
76
+ var DEFAULT_TOKENS = {
77
+ [STACKS_MAINNET_CAIP2]: SUSDC_MAINNET,
78
+ [STACKS_TESTNET_CAIP2]: SUSDC_TESTNET
79
+ };
80
+ function getTokenConfig(network, symbol = "sUSDC") {
81
+ return TOKEN_REGISTRY[network]?.[symbol];
82
+ }
83
+ function getContractAddress(network, symbol = "sUSDC") {
84
+ return getTokenConfig(network, symbol)?.contractAddress;
85
+ }
86
+
87
+ // src/utils.ts
88
+ function isValidPrincipal(address) {
89
+ if (!address || typeof address !== "string") {
90
+ return false;
91
+ }
92
+ const parts = address.split(".");
93
+ const principal = parts[0];
94
+ const principalRegex = /^(SP|ST)[0-9A-HJ-NP-Za-km-z]{33,41}$/;
95
+ if (!principalRegex.test(principal)) {
96
+ return false;
97
+ }
98
+ if (parts.length === 2) {
99
+ const contractName = parts[1];
100
+ const contractNameRegex = /^[a-zA-Z][a-zA-Z0-9\-_]{0,127}$/;
101
+ return contractNameRegex.test(contractName);
102
+ }
103
+ return parts.length === 1;
104
+ }
105
+
106
+ // src/exact-direct/client/scheme.ts
107
+ var ExactDirectStacksClient = class {
108
+ scheme = SCHEME_EXACT_DIRECT;
109
+ signer;
110
+ constructor(config) {
111
+ this.signer = config.signer;
112
+ }
113
+ /**
114
+ * Create a payment payload by executing the transfer
115
+ */
116
+ async createPaymentPayload(t402Version, requirements) {
117
+ this.validateRequirements(requirements);
118
+ const { network, amount, payTo, extra } = requirements;
119
+ const symbol = extra?.assetSymbol || "sUSDC";
120
+ const contractAddress = extra?.contractAddress ?? getContractAddress(network, symbol);
121
+ if (!contractAddress) {
122
+ throw new Error(`Unknown asset ${symbol} on network ${network}`);
123
+ }
124
+ const from = await this.signer.getAddress();
125
+ const { txId } = await this.signer.transferToken(contractAddress, payTo, amount);
126
+ const stacksPayload = {
127
+ txId,
128
+ from,
129
+ to: payTo,
130
+ amount,
131
+ contractAddress
132
+ };
133
+ return {
134
+ t402Version,
135
+ payload: stacksPayload
136
+ };
137
+ }
138
+ /**
139
+ * Validate payment requirements
140
+ */
141
+ validateRequirements(requirements) {
142
+ if (requirements.scheme !== SCHEME_EXACT_DIRECT) {
143
+ throw new Error(
144
+ `Invalid scheme: expected ${SCHEME_EXACT_DIRECT}, got ${requirements.scheme}`
145
+ );
146
+ }
147
+ if (!requirements.network.startsWith(`${STACKS_CAIP2_NAMESPACE}:`)) {
148
+ throw new Error(`Invalid network: ${requirements.network}`);
149
+ }
150
+ if (!isValidPrincipal(requirements.payTo)) {
151
+ throw new Error(`Invalid payTo address: ${requirements.payTo}`);
152
+ }
153
+ const amount = BigInt(requirements.amount);
154
+ if (amount <= 0n) {
155
+ throw new Error(`Invalid amount: ${requirements.amount}`);
156
+ }
157
+ }
158
+ };
159
+ function createExactDirectStacksClient(config) {
160
+ return new ExactDirectStacksClient(config);
161
+ }
162
+ // Annotate the CommonJS export names for ESM import in node:
163
+ 0 && (module.exports = {
164
+ ExactDirectStacksClient,
165
+ createExactDirectStacksClient
166
+ });
167
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/exact-direct/client/index.ts","../../../src/constants.ts","../../../src/tokens.ts","../../../src/utils.ts","../../../src/exact-direct/client/scheme.ts"],"sourcesContent":["export {\n ExactDirectStacksClient,\n createExactDirectStacksClient,\n type ExactDirectStacksClientConfig,\n} from \"./scheme.js\";\n","/**\n * Stacks T402 Constants\n *\n * Stacks is a Bitcoin Layer 2 that brings smart contracts and DeFi\n * to Bitcoin. SIP-010 is the fungible token standard on Stacks.\n */\n\n// CAIP-2 namespace for Stacks\nexport const STACKS_CAIP2_NAMESPACE = \"stacks\";\n\n// CAIP-2 network identifiers\n// Stacks Mainnet (chain ID: 1)\nexport const STACKS_MAINNET_CAIP2 = \"stacks:1\";\n\n// Stacks Testnet (chain ID: 2147483648)\nexport const STACKS_TESTNET_CAIP2 = \"stacks:2147483648\";\n\n// Scheme identifier\nexport const SCHEME_EXACT_DIRECT = \"exact-direct\";\n\n// Default API endpoints (Hiro API)\nexport const DEFAULT_MAINNET_API = \"https://api.mainnet.hiro.so\";\nexport const DEFAULT_TESTNET_API = \"https://api.testnet.hiro.so\";\n\n// Network configurations\nexport interface StacksNetworkConfig {\n readonly name: string;\n readonly caip2: string;\n readonly apiUrl: string;\n readonly chainId: number;\n readonly addressPrefix: string;\n readonly isTestnet: boolean;\n}\n\nexport const STACKS_NETWORKS: Record<string, StacksNetworkConfig> = {\n [STACKS_MAINNET_CAIP2]: {\n name: \"Stacks Mainnet\",\n caip2: STACKS_MAINNET_CAIP2,\n apiUrl: DEFAULT_MAINNET_API,\n chainId: 1,\n addressPrefix: \"SP\",\n isTestnet: false,\n },\n [STACKS_TESTNET_CAIP2]: {\n name: \"Stacks Testnet\",\n caip2: STACKS_TESTNET_CAIP2,\n apiUrl: DEFAULT_TESTNET_API,\n chainId: 2147483648,\n addressPrefix: \"ST\",\n isTestnet: true,\n },\n};\n\n/**\n * Get network configuration by CAIP-2 identifier\n */\nexport function getNetworkConfig(network: string): StacksNetworkConfig | undefined {\n return STACKS_NETWORKS[network];\n}\n\n/**\n * Check if a network identifier is a Stacks network\n */\nexport function isStacksNetwork(network: string): boolean {\n return network.startsWith(`${STACKS_CAIP2_NAMESPACE}:`);\n}\n","/**\n * Stacks Token Registry\n *\n * On Stacks, tokens are SIP-010 fungible tokens identified by\n * their contract address (principal.contract-name).\n */\n\nimport {\n STACKS_MAINNET_CAIP2,\n STACKS_TESTNET_CAIP2,\n} from \"./constants.js\";\n\n/**\n * Token configuration for Stacks SIP-010 tokens\n */\nexport interface TokenConfig {\n /** Contract address (principal.contract-name) */\n readonly contractAddress: string;\n /** Token symbol */\n readonly symbol: string;\n /** Token name */\n readonly name: string;\n /** Decimal places */\n readonly decimals: number;\n /** Token issuer */\n readonly issuer?: string;\n}\n\n/**\n * sUSDC on Stacks Mainnet\n * Contract: SP3Y2ZSH8P7D50B0VBTSX11S7XSG24M1VB9YFQA4K.token-susdc\n * Decimals: 6\n */\nexport const SUSDC_MAINNET: TokenConfig = {\n contractAddress: \"SP3Y2ZSH8P7D50B0VBTSX11S7XSG24M1VB9YFQA4K.token-susdc\",\n symbol: \"sUSDC\",\n name: \"Stacks USDC\",\n decimals: 6,\n issuer: \"Stacks\",\n};\n\n/**\n * sUSDC on Stacks Testnet\n * Contract: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.token-susdc\n * Decimals: 6\n */\nexport const SUSDC_TESTNET: TokenConfig = {\n contractAddress: \"ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.token-susdc\",\n symbol: \"sUSDC\",\n name: \"Test Stacks USDC\",\n decimals: 6,\n};\n\n/**\n * Network-specific token registries\n */\nexport const TOKEN_REGISTRY: Record<string, Record<string, TokenConfig>> = {\n [STACKS_MAINNET_CAIP2]: {\n sUSDC: SUSDC_MAINNET,\n },\n [STACKS_TESTNET_CAIP2]: {\n sUSDC: SUSDC_TESTNET,\n },\n};\n\n/**\n * Default tokens per network\n */\nexport const DEFAULT_TOKENS: Record<string, TokenConfig> = {\n [STACKS_MAINNET_CAIP2]: SUSDC_MAINNET,\n [STACKS_TESTNET_CAIP2]: SUSDC_TESTNET,\n};\n\n/**\n * Get token configuration by network and symbol\n */\nexport function getTokenConfig(\n network: string,\n symbol: string = \"sUSDC\",\n): TokenConfig | undefined {\n return TOKEN_REGISTRY[network]?.[symbol];\n}\n\n/**\n * Get the default token for a network\n */\nexport function getDefaultToken(network: string): TokenConfig | undefined {\n return DEFAULT_TOKENS[network];\n}\n\n/**\n * Get contract address for a token on a network\n */\nexport function getContractAddress(network: string, symbol: string = \"sUSDC\"): string | undefined {\n return getTokenConfig(network, symbol)?.contractAddress;\n}\n","/**\n * Stacks Utility Functions\n */\n\nimport type { StacksTransactionResult, ParsedTokenTransfer } from \"./types.js\";\n\n/**\n * Validate a Stacks principal address format\n * Stacks addresses start with SP (mainnet) or ST (testnet)\n * followed by alphanumeric characters (base58-like encoding)\n */\nexport function isValidPrincipal(address: string): boolean {\n if (!address || typeof address !== \"string\") {\n return false;\n }\n\n // Standard principal: SP/ST prefix + base58 characters (33-41 chars total)\n // Contract principal: standard-principal.contract-name\n const parts = address.split(\".\");\n const principal = parts[0];\n\n // Check principal format: SP or ST prefix + alphanumeric (base58 chars)\n const principalRegex = /^(SP|ST)[0-9A-HJ-NP-Za-km-z]{33,41}$/;\n if (!principalRegex.test(principal)) {\n return false;\n }\n\n // If it's a contract principal, validate contract name\n if (parts.length === 2) {\n const contractName = parts[1];\n // Contract names: 1-128 chars, alphanumeric + hyphen + underscore\n const contractNameRegex = /^[a-zA-Z][a-zA-Z0-9\\-_]{0,127}$/;\n return contractNameRegex.test(contractName);\n }\n\n // Standard principal (no contract part) or exactly one dot for contract\n return parts.length === 1;\n}\n\n/**\n * Validate a Stacks transaction ID format\n * Transaction IDs are 0x-prefixed 64-character hex strings\n */\nexport function isValidTxId(hash: string): boolean {\n if (!hash || typeof hash !== \"string\") {\n return false;\n }\n return /^0x[a-fA-F0-9]{64}$/.test(hash);\n}\n\n/**\n * Compare two Stacks principals (case-sensitive)\n */\nexport function comparePrincipals(a: string, b: string): boolean {\n return a === b;\n}\n\n/**\n * Format an amount with decimals for display\n */\nexport function formatAmount(amount: string, decimals: number): string {\n const amountBigInt = BigInt(amount);\n const divisor = BigInt(10 ** decimals);\n const wholePart = amountBigInt / divisor;\n const fractionalPart = amountBigInt % divisor;\n\n if (fractionalPart === 0n) {\n return wholePart.toString();\n }\n\n const fractionalStr = fractionalPart.toString().padStart(decimals, \"0\");\n const trimmedFractional = fractionalStr.replace(/0+$/, \"\");\n return `${wholePart}.${trimmedFractional}`;\n}\n\n/**\n * Parse an amount string to the smallest unit (with decimals applied)\n */\nexport function parseAmount(amount: string, decimals: number): string {\n const parts = amount.split(\".\");\n const wholePart = parts[0] || \"0\";\n const fractionalPart = (parts[1] || \"\").padEnd(decimals, \"0\").slice(0, decimals);\n return BigInt(wholePart + fractionalPart).toString();\n}\n\n/**\n * Extract token transfer details from a Stacks transaction result\n * Looks for ft_transfer events matching the expected contract\n */\nexport function extractTokenTransfer(\n result: StacksTransactionResult,\n contractAddress?: string,\n): ParsedTokenTransfer | null {\n if (result.txStatus !== \"success\") {\n return null;\n }\n\n // Check if this is a contract-call transaction\n if (result.txType !== \"contract_call\") {\n return null;\n }\n\n // Check contract call is a transfer function\n if (result.contractCall) {\n const { contractId, functionName } = result.contractCall;\n\n if (functionName !== \"transfer\") {\n return null;\n }\n\n // If contractAddress specified, verify it matches\n if (contractAddress && contractId !== contractAddress) {\n return null;\n }\n\n // Look for ft_transfer event\n const transferEvent = result.events.find(\n (e) => e.eventType === \"fungible_token_asset\" && e.asset?.assetEventType === \"transfer\",\n );\n\n if (transferEvent?.asset) {\n return {\n contractAddress: contractId,\n from: transferEvent.asset.sender,\n to: transferEvent.asset.recipient,\n amount: transferEvent.asset.amount,\n success: true,\n };\n }\n\n // Fallback: extract from function args if events not available\n if (result.contractCall.functionArgs.length >= 3) {\n const amountArg = result.contractCall.functionArgs[0];\n const senderArg = result.contractCall.functionArgs[1];\n const recipientArg = result.contractCall.functionArgs[2];\n\n // Parse principal from repr (format: 'SP...')\n const senderMatch = senderArg?.repr?.match(/^'?(S[PT][0-9A-HJ-NP-Za-km-z]+)/);\n const recipientMatch = recipientArg?.repr?.match(/^'?(S[PT][0-9A-HJ-NP-Za-km-z]+)/);\n const amountMatch = amountArg?.repr?.match(/^u(\\d+)$/);\n\n if (senderMatch && recipientMatch && amountMatch) {\n return {\n contractAddress: contractId,\n from: senderMatch[1],\n to: recipientMatch[1],\n amount: amountMatch[1],\n success: true,\n };\n }\n }\n }\n\n return null;\n}\n\n/**\n * Extract token transfer from post conditions (alternative method)\n */\nexport function extractTokenTransferFromPostConditions(\n result: StacksTransactionResult,\n contractAddress?: string,\n): ParsedTokenTransfer | null {\n if (result.txStatus !== \"success\") {\n return null;\n }\n\n // Look for fungible token post conditions\n for (const pc of result.postConditions) {\n if (pc.asset) {\n const assetContractAddress = `${pc.asset.contractAddress}.${pc.asset.contractName}`;\n\n if (contractAddress && assetContractAddress !== contractAddress) {\n continue;\n }\n\n // Find corresponding ft_transfer event for recipient\n const transferEvent = result.events.find(\n (e) =>\n e.eventType === \"fungible_token_asset\" &&\n e.asset?.assetEventType === \"transfer\" &&\n e.asset?.sender === pc.principal.address,\n );\n\n if (transferEvent?.asset) {\n return {\n contractAddress: assetContractAddress,\n from: transferEvent.asset.sender,\n to: transferEvent.asset.recipient,\n amount: transferEvent.asset.amount,\n success: true,\n };\n }\n }\n }\n\n return null;\n}\n","/**\n * Stacks Exact-Direct Client Scheme\n *\n * In the exact-direct scheme, the client executes the SIP-010 token transfer\n * directly and provides the transaction ID as proof of payment.\n */\n\nimport type {\n PaymentPayload,\n PaymentRequirements,\n SchemeNetworkClient,\n} from \"@t402/core/types\";\nimport type { ClientStacksSigner, ExactDirectStacksPayload } from \"../../types.js\";\nimport { SCHEME_EXACT_DIRECT, STACKS_CAIP2_NAMESPACE } from \"../../constants.js\";\nimport { getContractAddress } from \"../../tokens.js\";\nimport { isValidPrincipal } from \"../../utils.js\";\n\n/**\n * Configuration for the exact-direct client\n */\nexport interface ExactDirectStacksClientConfig {\n /** Signer for executing transactions */\n signer: ClientStacksSigner;\n}\n\n/**\n * Exact-direct client scheme for Stacks\n */\nexport class ExactDirectStacksClient implements SchemeNetworkClient {\n readonly scheme = SCHEME_EXACT_DIRECT;\n private readonly signer: ClientStacksSigner;\n\n constructor(config: ExactDirectStacksClientConfig) {\n this.signer = config.signer;\n }\n\n /**\n * Create a payment payload by executing the transfer\n */\n async createPaymentPayload(\n t402Version: number,\n requirements: PaymentRequirements,\n ): Promise<Pick<PaymentPayload, \"t402Version\" | \"payload\">> {\n // Validate requirements\n this.validateRequirements(requirements);\n\n const { network, amount, payTo, extra } = requirements;\n\n // Get contract address from extra or use default sUSDC\n const symbol = (extra?.assetSymbol as string) || \"sUSDC\";\n const contractAddress =\n (extra?.contractAddress as string) ?? getContractAddress(network, symbol);\n\n if (!contractAddress) {\n throw new Error(`Unknown asset ${symbol} on network ${network}`);\n }\n\n // Get sender address\n const from = await this.signer.getAddress();\n\n // Execute the transfer\n const { txId } = await this.signer.transferToken(contractAddress, payTo, amount);\n\n // Build the payload\n const stacksPayload: ExactDirectStacksPayload = {\n txId,\n from,\n to: payTo,\n amount,\n contractAddress,\n };\n\n return {\n t402Version,\n payload: stacksPayload,\n };\n }\n\n /**\n * Validate payment requirements\n */\n private validateRequirements(requirements: PaymentRequirements): void {\n // Check scheme\n if (requirements.scheme !== SCHEME_EXACT_DIRECT) {\n throw new Error(\n `Invalid scheme: expected ${SCHEME_EXACT_DIRECT}, got ${requirements.scheme}`,\n );\n }\n\n // Check network\n if (!requirements.network.startsWith(`${STACKS_CAIP2_NAMESPACE}:`)) {\n throw new Error(`Invalid network: ${requirements.network}`);\n }\n\n // Check payTo address\n if (!isValidPrincipal(requirements.payTo)) {\n throw new Error(`Invalid payTo address: ${requirements.payTo}`);\n }\n\n // Check amount\n const amount = BigInt(requirements.amount);\n if (amount <= 0n) {\n throw new Error(`Invalid amount: ${requirements.amount}`);\n }\n }\n}\n\n/**\n * Create an exact-direct client for Stacks\n */\nexport function createExactDirectStacksClient(\n config: ExactDirectStacksClientConfig,\n): ExactDirectStacksClient {\n return new ExactDirectStacksClient(config);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,39 @@
1
+ import { SchemeNetworkClient, PaymentRequirements, PaymentPayload } from '@t402/core/types';
2
+ import { C as ClientStacksSigner } from '../../types-Bxzo3eQ1.cjs';
3
+
4
+ /**
5
+ * Stacks Exact-Direct Client Scheme
6
+ *
7
+ * In the exact-direct scheme, the client executes the SIP-010 token transfer
8
+ * directly and provides the transaction ID as proof of payment.
9
+ */
10
+
11
+ /**
12
+ * Configuration for the exact-direct client
13
+ */
14
+ interface ExactDirectStacksClientConfig {
15
+ /** Signer for executing transactions */
16
+ signer: ClientStacksSigner;
17
+ }
18
+ /**
19
+ * Exact-direct client scheme for Stacks
20
+ */
21
+ declare class ExactDirectStacksClient implements SchemeNetworkClient {
22
+ readonly scheme = "exact-direct";
23
+ private readonly signer;
24
+ constructor(config: ExactDirectStacksClientConfig);
25
+ /**
26
+ * Create a payment payload by executing the transfer
27
+ */
28
+ createPaymentPayload(t402Version: number, requirements: PaymentRequirements): Promise<Pick<PaymentPayload, "t402Version" | "payload">>;
29
+ /**
30
+ * Validate payment requirements
31
+ */
32
+ private validateRequirements;
33
+ }
34
+ /**
35
+ * Create an exact-direct client for Stacks
36
+ */
37
+ declare function createExactDirectStacksClient(config: ExactDirectStacksClientConfig): ExactDirectStacksClient;
38
+
39
+ export { ExactDirectStacksClient, type ExactDirectStacksClientConfig, createExactDirectStacksClient };
@@ -0,0 +1,39 @@
1
+ import { SchemeNetworkClient, PaymentRequirements, PaymentPayload } from '@t402/core/types';
2
+ import { C as ClientStacksSigner } from '../../types-Bxzo3eQ1.js';
3
+
4
+ /**
5
+ * Stacks Exact-Direct Client Scheme
6
+ *
7
+ * In the exact-direct scheme, the client executes the SIP-010 token transfer
8
+ * directly and provides the transaction ID as proof of payment.
9
+ */
10
+
11
+ /**
12
+ * Configuration for the exact-direct client
13
+ */
14
+ interface ExactDirectStacksClientConfig {
15
+ /** Signer for executing transactions */
16
+ signer: ClientStacksSigner;
17
+ }
18
+ /**
19
+ * Exact-direct client scheme for Stacks
20
+ */
21
+ declare class ExactDirectStacksClient implements SchemeNetworkClient {
22
+ readonly scheme = "exact-direct";
23
+ private readonly signer;
24
+ constructor(config: ExactDirectStacksClientConfig);
25
+ /**
26
+ * Create a payment payload by executing the transfer
27
+ */
28
+ createPaymentPayload(t402Version: number, requirements: PaymentRequirements): Promise<Pick<PaymentPayload, "t402Version" | "payload">>;
29
+ /**
30
+ * Validate payment requirements
31
+ */
32
+ private validateRequirements;
33
+ }
34
+ /**
35
+ * Create an exact-direct client for Stacks
36
+ */
37
+ declare function createExactDirectStacksClient(config: ExactDirectStacksClientConfig): ExactDirectStacksClient;
38
+
39
+ export { ExactDirectStacksClient, type ExactDirectStacksClientConfig, createExactDirectStacksClient };
@@ -0,0 +1,139 @@
1
+ // src/constants.ts
2
+ var STACKS_CAIP2_NAMESPACE = "stacks";
3
+ var STACKS_MAINNET_CAIP2 = "stacks:1";
4
+ var STACKS_TESTNET_CAIP2 = "stacks:2147483648";
5
+ var SCHEME_EXACT_DIRECT = "exact-direct";
6
+ var DEFAULT_MAINNET_API = "https://api.mainnet.hiro.so";
7
+ var DEFAULT_TESTNET_API = "https://api.testnet.hiro.so";
8
+ var STACKS_NETWORKS = {
9
+ [STACKS_MAINNET_CAIP2]: {
10
+ name: "Stacks Mainnet",
11
+ caip2: STACKS_MAINNET_CAIP2,
12
+ apiUrl: DEFAULT_MAINNET_API,
13
+ chainId: 1,
14
+ addressPrefix: "SP",
15
+ isTestnet: false
16
+ },
17
+ [STACKS_TESTNET_CAIP2]: {
18
+ name: "Stacks Testnet",
19
+ caip2: STACKS_TESTNET_CAIP2,
20
+ apiUrl: DEFAULT_TESTNET_API,
21
+ chainId: 2147483648,
22
+ addressPrefix: "ST",
23
+ isTestnet: true
24
+ }
25
+ };
26
+
27
+ // src/tokens.ts
28
+ var SUSDC_MAINNET = {
29
+ contractAddress: "SP3Y2ZSH8P7D50B0VBTSX11S7XSG24M1VB9YFQA4K.token-susdc",
30
+ symbol: "sUSDC",
31
+ name: "Stacks USDC",
32
+ decimals: 6,
33
+ issuer: "Stacks"
34
+ };
35
+ var SUSDC_TESTNET = {
36
+ contractAddress: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.token-susdc",
37
+ symbol: "sUSDC",
38
+ name: "Test Stacks USDC",
39
+ decimals: 6
40
+ };
41
+ var TOKEN_REGISTRY = {
42
+ [STACKS_MAINNET_CAIP2]: {
43
+ sUSDC: SUSDC_MAINNET
44
+ },
45
+ [STACKS_TESTNET_CAIP2]: {
46
+ sUSDC: SUSDC_TESTNET
47
+ }
48
+ };
49
+ var DEFAULT_TOKENS = {
50
+ [STACKS_MAINNET_CAIP2]: SUSDC_MAINNET,
51
+ [STACKS_TESTNET_CAIP2]: SUSDC_TESTNET
52
+ };
53
+ function getTokenConfig(network, symbol = "sUSDC") {
54
+ return TOKEN_REGISTRY[network]?.[symbol];
55
+ }
56
+ function getContractAddress(network, symbol = "sUSDC") {
57
+ return getTokenConfig(network, symbol)?.contractAddress;
58
+ }
59
+
60
+ // src/utils.ts
61
+ function isValidPrincipal(address) {
62
+ if (!address || typeof address !== "string") {
63
+ return false;
64
+ }
65
+ const parts = address.split(".");
66
+ const principal = parts[0];
67
+ const principalRegex = /^(SP|ST)[0-9A-HJ-NP-Za-km-z]{33,41}$/;
68
+ if (!principalRegex.test(principal)) {
69
+ return false;
70
+ }
71
+ if (parts.length === 2) {
72
+ const contractName = parts[1];
73
+ const contractNameRegex = /^[a-zA-Z][a-zA-Z0-9\-_]{0,127}$/;
74
+ return contractNameRegex.test(contractName);
75
+ }
76
+ return parts.length === 1;
77
+ }
78
+
79
+ // src/exact-direct/client/scheme.ts
80
+ var ExactDirectStacksClient = class {
81
+ scheme = SCHEME_EXACT_DIRECT;
82
+ signer;
83
+ constructor(config) {
84
+ this.signer = config.signer;
85
+ }
86
+ /**
87
+ * Create a payment payload by executing the transfer
88
+ */
89
+ async createPaymentPayload(t402Version, requirements) {
90
+ this.validateRequirements(requirements);
91
+ const { network, amount, payTo, extra } = requirements;
92
+ const symbol = extra?.assetSymbol || "sUSDC";
93
+ const contractAddress = extra?.contractAddress ?? getContractAddress(network, symbol);
94
+ if (!contractAddress) {
95
+ throw new Error(`Unknown asset ${symbol} on network ${network}`);
96
+ }
97
+ const from = await this.signer.getAddress();
98
+ const { txId } = await this.signer.transferToken(contractAddress, payTo, amount);
99
+ const stacksPayload = {
100
+ txId,
101
+ from,
102
+ to: payTo,
103
+ amount,
104
+ contractAddress
105
+ };
106
+ return {
107
+ t402Version,
108
+ payload: stacksPayload
109
+ };
110
+ }
111
+ /**
112
+ * Validate payment requirements
113
+ */
114
+ validateRequirements(requirements) {
115
+ if (requirements.scheme !== SCHEME_EXACT_DIRECT) {
116
+ throw new Error(
117
+ `Invalid scheme: expected ${SCHEME_EXACT_DIRECT}, got ${requirements.scheme}`
118
+ );
119
+ }
120
+ if (!requirements.network.startsWith(`${STACKS_CAIP2_NAMESPACE}:`)) {
121
+ throw new Error(`Invalid network: ${requirements.network}`);
122
+ }
123
+ if (!isValidPrincipal(requirements.payTo)) {
124
+ throw new Error(`Invalid payTo address: ${requirements.payTo}`);
125
+ }
126
+ const amount = BigInt(requirements.amount);
127
+ if (amount <= 0n) {
128
+ throw new Error(`Invalid amount: ${requirements.amount}`);
129
+ }
130
+ }
131
+ };
132
+ function createExactDirectStacksClient(config) {
133
+ return new ExactDirectStacksClient(config);
134
+ }
135
+ export {
136
+ ExactDirectStacksClient,
137
+ createExactDirectStacksClient
138
+ };
139
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/constants.ts","../../../src/tokens.ts","../../../src/utils.ts","../../../src/exact-direct/client/scheme.ts"],"sourcesContent":["/**\n * Stacks T402 Constants\n *\n * Stacks is a Bitcoin Layer 2 that brings smart contracts and DeFi\n * to Bitcoin. SIP-010 is the fungible token standard on Stacks.\n */\n\n// CAIP-2 namespace for Stacks\nexport const STACKS_CAIP2_NAMESPACE = \"stacks\";\n\n// CAIP-2 network identifiers\n// Stacks Mainnet (chain ID: 1)\nexport const STACKS_MAINNET_CAIP2 = \"stacks:1\";\n\n// Stacks Testnet (chain ID: 2147483648)\nexport const STACKS_TESTNET_CAIP2 = \"stacks:2147483648\";\n\n// Scheme identifier\nexport const SCHEME_EXACT_DIRECT = \"exact-direct\";\n\n// Default API endpoints (Hiro API)\nexport const DEFAULT_MAINNET_API = \"https://api.mainnet.hiro.so\";\nexport const DEFAULT_TESTNET_API = \"https://api.testnet.hiro.so\";\n\n// Network configurations\nexport interface StacksNetworkConfig {\n readonly name: string;\n readonly caip2: string;\n readonly apiUrl: string;\n readonly chainId: number;\n readonly addressPrefix: string;\n readonly isTestnet: boolean;\n}\n\nexport const STACKS_NETWORKS: Record<string, StacksNetworkConfig> = {\n [STACKS_MAINNET_CAIP2]: {\n name: \"Stacks Mainnet\",\n caip2: STACKS_MAINNET_CAIP2,\n apiUrl: DEFAULT_MAINNET_API,\n chainId: 1,\n addressPrefix: \"SP\",\n isTestnet: false,\n },\n [STACKS_TESTNET_CAIP2]: {\n name: \"Stacks Testnet\",\n caip2: STACKS_TESTNET_CAIP2,\n apiUrl: DEFAULT_TESTNET_API,\n chainId: 2147483648,\n addressPrefix: \"ST\",\n isTestnet: true,\n },\n};\n\n/**\n * Get network configuration by CAIP-2 identifier\n */\nexport function getNetworkConfig(network: string): StacksNetworkConfig | undefined {\n return STACKS_NETWORKS[network];\n}\n\n/**\n * Check if a network identifier is a Stacks network\n */\nexport function isStacksNetwork(network: string): boolean {\n return network.startsWith(`${STACKS_CAIP2_NAMESPACE}:`);\n}\n","/**\n * Stacks Token Registry\n *\n * On Stacks, tokens are SIP-010 fungible tokens identified by\n * their contract address (principal.contract-name).\n */\n\nimport {\n STACKS_MAINNET_CAIP2,\n STACKS_TESTNET_CAIP2,\n} from \"./constants.js\";\n\n/**\n * Token configuration for Stacks SIP-010 tokens\n */\nexport interface TokenConfig {\n /** Contract address (principal.contract-name) */\n readonly contractAddress: string;\n /** Token symbol */\n readonly symbol: string;\n /** Token name */\n readonly name: string;\n /** Decimal places */\n readonly decimals: number;\n /** Token issuer */\n readonly issuer?: string;\n}\n\n/**\n * sUSDC on Stacks Mainnet\n * Contract: SP3Y2ZSH8P7D50B0VBTSX11S7XSG24M1VB9YFQA4K.token-susdc\n * Decimals: 6\n */\nexport const SUSDC_MAINNET: TokenConfig = {\n contractAddress: \"SP3Y2ZSH8P7D50B0VBTSX11S7XSG24M1VB9YFQA4K.token-susdc\",\n symbol: \"sUSDC\",\n name: \"Stacks USDC\",\n decimals: 6,\n issuer: \"Stacks\",\n};\n\n/**\n * sUSDC on Stacks Testnet\n * Contract: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.token-susdc\n * Decimals: 6\n */\nexport const SUSDC_TESTNET: TokenConfig = {\n contractAddress: \"ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.token-susdc\",\n symbol: \"sUSDC\",\n name: \"Test Stacks USDC\",\n decimals: 6,\n};\n\n/**\n * Network-specific token registries\n */\nexport const TOKEN_REGISTRY: Record<string, Record<string, TokenConfig>> = {\n [STACKS_MAINNET_CAIP2]: {\n sUSDC: SUSDC_MAINNET,\n },\n [STACKS_TESTNET_CAIP2]: {\n sUSDC: SUSDC_TESTNET,\n },\n};\n\n/**\n * Default tokens per network\n */\nexport const DEFAULT_TOKENS: Record<string, TokenConfig> = {\n [STACKS_MAINNET_CAIP2]: SUSDC_MAINNET,\n [STACKS_TESTNET_CAIP2]: SUSDC_TESTNET,\n};\n\n/**\n * Get token configuration by network and symbol\n */\nexport function getTokenConfig(\n network: string,\n symbol: string = \"sUSDC\",\n): TokenConfig | undefined {\n return TOKEN_REGISTRY[network]?.[symbol];\n}\n\n/**\n * Get the default token for a network\n */\nexport function getDefaultToken(network: string): TokenConfig | undefined {\n return DEFAULT_TOKENS[network];\n}\n\n/**\n * Get contract address for a token on a network\n */\nexport function getContractAddress(network: string, symbol: string = \"sUSDC\"): string | undefined {\n return getTokenConfig(network, symbol)?.contractAddress;\n}\n","/**\n * Stacks Utility Functions\n */\n\nimport type { StacksTransactionResult, ParsedTokenTransfer } from \"./types.js\";\n\n/**\n * Validate a Stacks principal address format\n * Stacks addresses start with SP (mainnet) or ST (testnet)\n * followed by alphanumeric characters (base58-like encoding)\n */\nexport function isValidPrincipal(address: string): boolean {\n if (!address || typeof address !== \"string\") {\n return false;\n }\n\n // Standard principal: SP/ST prefix + base58 characters (33-41 chars total)\n // Contract principal: standard-principal.contract-name\n const parts = address.split(\".\");\n const principal = parts[0];\n\n // Check principal format: SP or ST prefix + alphanumeric (base58 chars)\n const principalRegex = /^(SP|ST)[0-9A-HJ-NP-Za-km-z]{33,41}$/;\n if (!principalRegex.test(principal)) {\n return false;\n }\n\n // If it's a contract principal, validate contract name\n if (parts.length === 2) {\n const contractName = parts[1];\n // Contract names: 1-128 chars, alphanumeric + hyphen + underscore\n const contractNameRegex = /^[a-zA-Z][a-zA-Z0-9\\-_]{0,127}$/;\n return contractNameRegex.test(contractName);\n }\n\n // Standard principal (no contract part) or exactly one dot for contract\n return parts.length === 1;\n}\n\n/**\n * Validate a Stacks transaction ID format\n * Transaction IDs are 0x-prefixed 64-character hex strings\n */\nexport function isValidTxId(hash: string): boolean {\n if (!hash || typeof hash !== \"string\") {\n return false;\n }\n return /^0x[a-fA-F0-9]{64}$/.test(hash);\n}\n\n/**\n * Compare two Stacks principals (case-sensitive)\n */\nexport function comparePrincipals(a: string, b: string): boolean {\n return a === b;\n}\n\n/**\n * Format an amount with decimals for display\n */\nexport function formatAmount(amount: string, decimals: number): string {\n const amountBigInt = BigInt(amount);\n const divisor = BigInt(10 ** decimals);\n const wholePart = amountBigInt / divisor;\n const fractionalPart = amountBigInt % divisor;\n\n if (fractionalPart === 0n) {\n return wholePart.toString();\n }\n\n const fractionalStr = fractionalPart.toString().padStart(decimals, \"0\");\n const trimmedFractional = fractionalStr.replace(/0+$/, \"\");\n return `${wholePart}.${trimmedFractional}`;\n}\n\n/**\n * Parse an amount string to the smallest unit (with decimals applied)\n */\nexport function parseAmount(amount: string, decimals: number): string {\n const parts = amount.split(\".\");\n const wholePart = parts[0] || \"0\";\n const fractionalPart = (parts[1] || \"\").padEnd(decimals, \"0\").slice(0, decimals);\n return BigInt(wholePart + fractionalPart).toString();\n}\n\n/**\n * Extract token transfer details from a Stacks transaction result\n * Looks for ft_transfer events matching the expected contract\n */\nexport function extractTokenTransfer(\n result: StacksTransactionResult,\n contractAddress?: string,\n): ParsedTokenTransfer | null {\n if (result.txStatus !== \"success\") {\n return null;\n }\n\n // Check if this is a contract-call transaction\n if (result.txType !== \"contract_call\") {\n return null;\n }\n\n // Check contract call is a transfer function\n if (result.contractCall) {\n const { contractId, functionName } = result.contractCall;\n\n if (functionName !== \"transfer\") {\n return null;\n }\n\n // If contractAddress specified, verify it matches\n if (contractAddress && contractId !== contractAddress) {\n return null;\n }\n\n // Look for ft_transfer event\n const transferEvent = result.events.find(\n (e) => e.eventType === \"fungible_token_asset\" && e.asset?.assetEventType === \"transfer\",\n );\n\n if (transferEvent?.asset) {\n return {\n contractAddress: contractId,\n from: transferEvent.asset.sender,\n to: transferEvent.asset.recipient,\n amount: transferEvent.asset.amount,\n success: true,\n };\n }\n\n // Fallback: extract from function args if events not available\n if (result.contractCall.functionArgs.length >= 3) {\n const amountArg = result.contractCall.functionArgs[0];\n const senderArg = result.contractCall.functionArgs[1];\n const recipientArg = result.contractCall.functionArgs[2];\n\n // Parse principal from repr (format: 'SP...')\n const senderMatch = senderArg?.repr?.match(/^'?(S[PT][0-9A-HJ-NP-Za-km-z]+)/);\n const recipientMatch = recipientArg?.repr?.match(/^'?(S[PT][0-9A-HJ-NP-Za-km-z]+)/);\n const amountMatch = amountArg?.repr?.match(/^u(\\d+)$/);\n\n if (senderMatch && recipientMatch && amountMatch) {\n return {\n contractAddress: contractId,\n from: senderMatch[1],\n to: recipientMatch[1],\n amount: amountMatch[1],\n success: true,\n };\n }\n }\n }\n\n return null;\n}\n\n/**\n * Extract token transfer from post conditions (alternative method)\n */\nexport function extractTokenTransferFromPostConditions(\n result: StacksTransactionResult,\n contractAddress?: string,\n): ParsedTokenTransfer | null {\n if (result.txStatus !== \"success\") {\n return null;\n }\n\n // Look for fungible token post conditions\n for (const pc of result.postConditions) {\n if (pc.asset) {\n const assetContractAddress = `${pc.asset.contractAddress}.${pc.asset.contractName}`;\n\n if (contractAddress && assetContractAddress !== contractAddress) {\n continue;\n }\n\n // Find corresponding ft_transfer event for recipient\n const transferEvent = result.events.find(\n (e) =>\n e.eventType === \"fungible_token_asset\" &&\n e.asset?.assetEventType === \"transfer\" &&\n e.asset?.sender === pc.principal.address,\n );\n\n if (transferEvent?.asset) {\n return {\n contractAddress: assetContractAddress,\n from: transferEvent.asset.sender,\n to: transferEvent.asset.recipient,\n amount: transferEvent.asset.amount,\n success: true,\n };\n }\n }\n }\n\n return null;\n}\n","/**\n * Stacks Exact-Direct Client Scheme\n *\n * In the exact-direct scheme, the client executes the SIP-010 token transfer\n * directly and provides the transaction ID as proof of payment.\n */\n\nimport type {\n PaymentPayload,\n PaymentRequirements,\n SchemeNetworkClient,\n} from \"@t402/core/types\";\nimport type { ClientStacksSigner, ExactDirectStacksPayload } from \"../../types.js\";\nimport { SCHEME_EXACT_DIRECT, STACKS_CAIP2_NAMESPACE } from \"../../constants.js\";\nimport { getContractAddress } from \"../../tokens.js\";\nimport { isValidPrincipal } from \"../../utils.js\";\n\n/**\n * Configuration for the exact-direct client\n */\nexport interface ExactDirectStacksClientConfig {\n /** Signer for executing transactions */\n signer: ClientStacksSigner;\n}\n\n/**\n * Exact-direct client scheme for Stacks\n */\nexport class ExactDirectStacksClient implements SchemeNetworkClient {\n readonly scheme = SCHEME_EXACT_DIRECT;\n private readonly signer: ClientStacksSigner;\n\n constructor(config: ExactDirectStacksClientConfig) {\n this.signer = config.signer;\n }\n\n /**\n * Create a payment payload by executing the transfer\n */\n async createPaymentPayload(\n t402Version: number,\n requirements: PaymentRequirements,\n ): Promise<Pick<PaymentPayload, \"t402Version\" | \"payload\">> {\n // Validate requirements\n this.validateRequirements(requirements);\n\n const { network, amount, payTo, extra } = requirements;\n\n // Get contract address from extra or use default sUSDC\n const symbol = (extra?.assetSymbol as string) || \"sUSDC\";\n const contractAddress =\n (extra?.contractAddress as string) ?? getContractAddress(network, symbol);\n\n if (!contractAddress) {\n throw new Error(`Unknown asset ${symbol} on network ${network}`);\n }\n\n // Get sender address\n const from = await this.signer.getAddress();\n\n // Execute the transfer\n const { txId } = await this.signer.transferToken(contractAddress, payTo, amount);\n\n // Build the payload\n const stacksPayload: ExactDirectStacksPayload = {\n txId,\n from,\n to: payTo,\n amount,\n contractAddress,\n };\n\n return {\n t402Version,\n payload: stacksPayload,\n };\n }\n\n /**\n * Validate payment requirements\n */\n private validateRequirements(requirements: PaymentRequirements): void {\n // Check scheme\n if (requirements.scheme !== SCHEME_EXACT_DIRECT) {\n throw new Error(\n `Invalid scheme: expected ${SCHEME_EXACT_DIRECT}, got ${requirements.scheme}`,\n );\n }\n\n // Check network\n if (!requirements.network.startsWith(`${STACKS_CAIP2_NAMESPACE}:`)) {\n throw new Error(`Invalid network: ${requirements.network}`);\n }\n\n // Check payTo address\n if (!isValidPrincipal(requirements.payTo)) {\n throw new Error(`Invalid payTo address: ${requirements.payTo}`);\n }\n\n // Check amount\n const amount = BigInt(requirements.amount);\n if (amount <= 0n) {\n throw new Error(`Invalid amount: ${requirements.amount}`);\n }\n }\n}\n\n/**\n * Create an exact-direct client for Stacks\n */\nexport function createExactDirectStacksClient(\n config: ExactDirectStacksClientConfig,\n): ExactDirectStacksClient {\n return new ExactDirectStacksClient(config);\n}\n"],"mappings":"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","names":[]}