@sip-protocol/sdk 0.7.3 → 0.7.4
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 +267 -0
- package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
- package/dist/browser.d.mts +10 -4
- package/dist/browser.d.ts +10 -4
- package/dist/browser.js +47556 -19603
- package/dist/browser.mjs +628 -48
- package/dist/chunk-4GRJ5MAW.mjs +152 -0
- package/dist/chunk-5D7A3L3W.mjs +717 -0
- package/dist/chunk-64AYA5F5.mjs +7834 -0
- package/dist/chunk-GMDGB22A.mjs +379 -0
- package/dist/chunk-I534WKN7.mjs +328 -0
- package/dist/chunk-IBZVA5Y7.mjs +1003 -0
- package/dist/chunk-PRRZAWJE.mjs +223 -0
- package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
- package/dist/{chunk-3M3HNQCW.mjs → chunk-YWGJ77A2.mjs} +28656 -13103
- package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
- package/dist/constants-LHAAUC2T.mjs +51 -0
- package/dist/dist-2OGQ7FED.mjs +3957 -0
- package/dist/dist-IFHPYLDX.mjs +254 -0
- package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
- package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
- package/dist/{index-DIBZHOOQ.d.ts → index-DXh2IGkz.d.ts} +21239 -10304
- package/dist/{index-8MQz13eJ.d.mts → index-DeE1ZzA4.d.mts} +21239 -10304
- package/dist/index.d.mts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +48396 -19623
- package/dist/index.mjs +537 -19
- package/dist/interface-Bf7w1PLW.d.mts +679 -0
- package/dist/interface-Bf7w1PLW.d.ts +679 -0
- package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
- package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
- package/dist/proofs/halo2.d.mts +151 -0
- package/dist/proofs/halo2.d.ts +151 -0
- package/dist/proofs/halo2.js +350 -0
- package/dist/proofs/halo2.mjs +11 -0
- package/dist/proofs/kimchi.d.mts +160 -0
- package/dist/proofs/kimchi.d.ts +160 -0
- package/dist/proofs/kimchi.js +431 -0
- package/dist/proofs/kimchi.mjs +13 -0
- package/dist/proofs/noir.d.mts +1 -1
- package/dist/proofs/noir.d.ts +1 -1
- package/dist/proofs/noir.js +74 -18
- package/dist/proofs/noir.mjs +84 -24
- package/dist/solana-U3MEGU7W.mjs +280 -0
- package/dist/validity_proof-3POXLPNY.mjs +21 -0
- package/package.json +54 -21
- package/src/adapters/index.ts +41 -0
- package/src/adapters/jupiter.ts +571 -0
- package/src/adapters/near-intents.ts +135 -0
- package/src/advisor/advisor.ts +653 -0
- package/src/advisor/index.ts +54 -0
- package/src/advisor/tools.ts +303 -0
- package/src/advisor/types.ts +164 -0
- package/src/chains/ethereum/announcement.ts +536 -0
- package/src/chains/ethereum/bnb-optimizations.ts +474 -0
- package/src/chains/ethereum/commitment.ts +522 -0
- package/src/chains/ethereum/constants.ts +462 -0
- package/src/chains/ethereum/deployment.ts +596 -0
- package/src/chains/ethereum/gas-estimation.ts +538 -0
- package/src/chains/ethereum/index.ts +268 -0
- package/src/chains/ethereum/optimizations.ts +614 -0
- package/src/chains/ethereum/privacy-adapter.ts +855 -0
- package/src/chains/ethereum/registry.ts +584 -0
- package/src/chains/ethereum/rpc.ts +905 -0
- package/src/chains/ethereum/stealth.ts +491 -0
- package/src/chains/ethereum/token.ts +790 -0
- package/src/chains/ethereum/transfer.ts +637 -0
- package/src/chains/ethereum/types.ts +456 -0
- package/src/chains/ethereum/viewing-key.ts +455 -0
- package/src/chains/near/commitment.ts +608 -0
- package/src/chains/near/constants.ts +284 -0
- package/src/chains/near/function-call.ts +871 -0
- package/src/chains/near/history.ts +654 -0
- package/src/chains/near/implicit-account.ts +840 -0
- package/src/chains/near/index.ts +393 -0
- package/src/chains/near/native-transfer.ts +658 -0
- package/src/chains/near/nep141.ts +775 -0
- package/src/chains/near/privacy-adapter.ts +889 -0
- package/src/chains/near/resolver.ts +971 -0
- package/src/chains/near/rpc.ts +1016 -0
- package/src/chains/near/stealth.ts +419 -0
- package/src/chains/near/types.ts +317 -0
- package/src/chains/near/viewing-key.ts +876 -0
- package/src/chains/solana/anchor-transfer.ts +386 -0
- package/src/chains/solana/commitment.ts +577 -0
- package/src/chains/solana/constants.ts +126 -12
- package/src/chains/solana/ephemeral-keys.ts +543 -0
- package/src/chains/solana/index.ts +252 -1
- package/src/chains/solana/key-derivation.ts +418 -0
- package/src/chains/solana/kit-compat.ts +334 -0
- package/src/chains/solana/optimizations.ts +560 -0
- package/src/chains/solana/privacy-adapter.ts +605 -0
- package/src/chains/solana/providers/generic.ts +47 -6
- package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
- package/src/chains/solana/providers/helius-enhanced.ts +623 -0
- package/src/chains/solana/providers/helius.ts +186 -33
- package/src/chains/solana/providers/index.ts +31 -0
- package/src/chains/solana/providers/interface.ts +61 -18
- package/src/chains/solana/providers/quicknode.ts +409 -0
- package/src/chains/solana/providers/triton.ts +426 -0
- package/src/chains/solana/providers/webhook.ts +338 -67
- package/src/chains/solana/rpc-client.ts +1150 -0
- package/src/chains/solana/scan.ts +83 -66
- package/src/chains/solana/sol-transfer.ts +732 -0
- package/src/chains/solana/spl-transfer.ts +886 -0
- package/src/chains/solana/stealth-scanner.ts +703 -0
- package/src/chains/solana/sunspot-verifier.ts +453 -0
- package/src/chains/solana/transaction-builder.ts +755 -0
- package/src/chains/solana/transfer.ts +74 -5
- package/src/chains/solana/types.ts +57 -6
- package/src/chains/solana/utils.ts +110 -0
- package/src/chains/solana/viewing-key.ts +807 -0
- package/src/compliance/fireblocks.ts +921 -0
- package/src/compliance/index.ts +23 -0
- package/src/compliance/range-sas.ts +398 -33
- package/src/config/endpoints.ts +100 -0
- package/src/crypto.ts +11 -8
- package/src/errors.ts +82 -0
- package/src/evm/erc4337-relayer.ts +830 -0
- package/src/evm/index.ts +47 -0
- package/src/fees/calculator.ts +396 -0
- package/src/fees/index.ts +87 -0
- package/src/fees/near-contract.ts +429 -0
- package/src/fees/types.ts +268 -0
- package/src/index.ts +686 -1
- package/src/intent.ts +6 -3
- package/src/logger.ts +324 -0
- package/src/network/index.ts +80 -0
- package/src/network/proxy.ts +691 -0
- package/src/optimizations/index.ts +541 -0
- package/src/oracle/types.ts +1 -0
- package/src/privacy-backends/arcium-types.ts +727 -0
- package/src/privacy-backends/arcium.ts +719 -0
- package/src/privacy-backends/combined-privacy.ts +866 -0
- package/src/privacy-backends/cspl-token.ts +595 -0
- package/src/privacy-backends/cspl-types.ts +512 -0
- package/src/privacy-backends/cspl.ts +907 -0
- package/src/privacy-backends/health.ts +488 -0
- package/src/privacy-backends/inco-types.ts +323 -0
- package/src/privacy-backends/inco.ts +616 -0
- package/src/privacy-backends/index.ts +254 -4
- package/src/privacy-backends/interface.ts +649 -6
- package/src/privacy-backends/lru-cache.ts +343 -0
- package/src/privacy-backends/magicblock.ts +458 -0
- package/src/privacy-backends/mock.ts +258 -0
- package/src/privacy-backends/privacycash.ts +13 -17
- package/src/privacy-backends/private-swap.ts +570 -0
- package/src/privacy-backends/rate-limiter.ts +683 -0
- package/src/privacy-backends/registry.ts +414 -2
- package/src/privacy-backends/router.ts +283 -3
- package/src/privacy-backends/shadowwire.ts +449 -0
- package/src/privacy-backends/sip-native.ts +3 -0
- package/src/privacy-logger.ts +191 -0
- package/src/production-safety.ts +373 -0
- package/src/proofs/aggregator.ts +1029 -0
- package/src/proofs/browser-composer.ts +1150 -0
- package/src/proofs/browser.ts +113 -25
- package/src/proofs/cache/index.ts +127 -0
- package/src/proofs/cache/interface.ts +545 -0
- package/src/proofs/cache/key-generator.ts +188 -0
- package/src/proofs/cache/lru-cache.ts +481 -0
- package/src/proofs/cache/multi-tier-cache.ts +575 -0
- package/src/proofs/cache/persistent-cache.ts +788 -0
- package/src/proofs/compliance-proof.ts +872 -0
- package/src/proofs/composer/base.ts +923 -0
- package/src/proofs/composer/index.ts +25 -0
- package/src/proofs/composer/interface.ts +518 -0
- package/src/proofs/composer/types.ts +383 -0
- package/src/proofs/converters/halo2.ts +452 -0
- package/src/proofs/converters/index.ts +208 -0
- package/src/proofs/converters/interface.ts +363 -0
- package/src/proofs/converters/kimchi.ts +462 -0
- package/src/proofs/converters/noir.ts +451 -0
- package/src/proofs/fallback.ts +888 -0
- package/src/proofs/halo2.ts +42 -0
- package/src/proofs/index.ts +471 -0
- package/src/proofs/interface.ts +13 -0
- package/src/proofs/kimchi.ts +42 -0
- package/src/proofs/lazy.ts +1004 -0
- package/src/proofs/mock.ts +25 -1
- package/src/proofs/noir.ts +110 -29
- package/src/proofs/orchestrator.ts +960 -0
- package/src/proofs/parallel/concurrency.ts +297 -0
- package/src/proofs/parallel/dependency-graph.ts +602 -0
- package/src/proofs/parallel/executor.ts +420 -0
- package/src/proofs/parallel/index.ts +131 -0
- package/src/proofs/parallel/interface.ts +685 -0
- package/src/proofs/parallel/worker-pool.ts +644 -0
- package/src/proofs/providers/halo2.ts +560 -0
- package/src/proofs/providers/index.ts +34 -0
- package/src/proofs/providers/kimchi.ts +641 -0
- package/src/proofs/validator.ts +881 -0
- package/src/proofs/verifier.ts +867 -0
- package/src/quantum/index.ts +112 -0
- package/src/quantum/winternitz-vault.ts +639 -0
- package/src/quantum/wots.ts +611 -0
- package/src/settlement/backends/direct-chain.ts +1 -0
- package/src/settlement/index.ts +9 -0
- package/src/settlement/router.ts +732 -46
- package/src/solana/index.ts +72 -0
- package/src/solana/jito-relayer.ts +687 -0
- package/src/solana/noir-verifier-types.ts +430 -0
- package/src/solana/noir-verifier.ts +816 -0
- package/src/stealth/address-derivation.ts +193 -0
- package/src/stealth/ed25519.ts +431 -0
- package/src/stealth/index.ts +233 -0
- package/src/stealth/meta-address.ts +221 -0
- package/src/stealth/secp256k1.ts +368 -0
- package/src/stealth/utils.ts +194 -0
- package/src/stealth.ts +50 -1504
- package/src/sync/index.ts +106 -0
- package/src/sync/manager.ts +504 -0
- package/src/sync/mock-provider.ts +318 -0
- package/src/sync/oblivious.ts +625 -0
- package/src/tokens/index.ts +15 -0
- package/src/tokens/registry.ts +301 -0
- package/src/utils/deprecation.ts +94 -0
- package/src/utils/index.ts +9 -0
- package/src/wallet/ethereum/index.ts +68 -0
- package/src/wallet/ethereum/metamask-privacy.ts +420 -0
- package/src/wallet/ethereum/multi-wallet.ts +646 -0
- package/src/wallet/ethereum/privacy-adapter.ts +700 -0
- package/src/wallet/ethereum/types.ts +3 -1
- package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
- package/src/wallet/hardware/index.ts +10 -0
- package/src/wallet/hardware/ledger-privacy.ts +414 -0
- package/src/wallet/index.ts +71 -0
- package/src/wallet/near/adapter.ts +626 -0
- package/src/wallet/near/index.ts +86 -0
- package/src/wallet/near/meteor-wallet.ts +1153 -0
- package/src/wallet/near/my-near-wallet.ts +790 -0
- package/src/wallet/near/wallet-selector.ts +702 -0
- package/src/wallet/solana/adapter.ts +6 -4
- package/src/wallet/solana/index.ts +13 -0
- package/src/wallet/solana/privacy-adapter.ts +567 -0
- package/src/wallet/sui/types.ts +6 -4
- package/src/zcash/rpc-client.ts +13 -6
- package/dist/chunk-2XIVXWHA.mjs +0 -1930
- package/dist/chunk-3INS3PR5.mjs +0 -884
- package/dist/chunk-3OVABDRH.mjs +0 -17096
- package/dist/chunk-7RFRWDCW.mjs +0 -1504
- package/dist/chunk-DLDWZFYC.mjs +0 -1495
- package/dist/chunk-E6SZWREQ.mjs +0 -57
- package/dist/chunk-F6F73W35.mjs +0 -16166
- package/dist/chunk-G33LB27A.mjs +0 -16166
- package/dist/chunk-HGU6HZRC.mjs +0 -231
- package/dist/chunk-L2K34JCU.mjs +0 -1496
- package/dist/chunk-OFDBEIEK.mjs +0 -16166
- package/dist/chunk-SF7YSLF5.mjs +0 -1515
- package/dist/chunk-SN4ZDTVW.mjs +0 -16166
- package/dist/chunk-WWUSGOXE.mjs +0 -17129
- package/dist/constants-VOI7BSLK.mjs +0 -27
- package/dist/index-B71aXVzk.d.ts +0 -13264
- package/dist/index-BYZbDjal.d.ts +0 -11390
- package/dist/index-CHB3KuOB.d.mts +0 -11859
- package/dist/index-CzWPI6Le.d.ts +0 -11859
- package/dist/index-pOIIuwfV.d.mts +0 -13264
- package/dist/index-xbWjohNq.d.mts +0 -11390
- package/dist/solana-4O4K45VU.mjs +0 -46
- package/dist/solana-5EMCTPTS.mjs +0 -46
- package/dist/solana-NDABAZ6P.mjs +0 -56
- package/dist/solana-Q4NAVBTS.mjs +0 -46
- package/dist/solana-ZYO63LY5.mjs +0 -46
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NEAR Native Transfer with Stealth Addresses
|
|
3
|
+
*
|
|
4
|
+
* Privacy-preserving NEAR native token transfers using stealth addresses.
|
|
5
|
+
* Extends basic transfer functionality with:
|
|
6
|
+
* - Amount commitments for hidden transfer values
|
|
7
|
+
* - Batch transfers to multiple stealth addresses
|
|
8
|
+
* - Gas sponsorship for sender anonymity
|
|
9
|
+
* - Minimum balance handling for account creation
|
|
10
|
+
*
|
|
11
|
+
* @example Private NEAR transfer with commitment
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { buildPrivateNativeTransferWithCommitment } from '@sip-protocol/sdk'
|
|
14
|
+
*
|
|
15
|
+
* const result = buildPrivateNativeTransferWithCommitment({
|
|
16
|
+
* recipientMetaAddress: 'sip:near:0x...:0x...',
|
|
17
|
+
* amount: ONE_NEAR,
|
|
18
|
+
* hideAmount: true,
|
|
19
|
+
* })
|
|
20
|
+
*
|
|
21
|
+
* // result.commitment contains the hidden amount
|
|
22
|
+
* // result.transfer contains the transaction actions
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @example Batch NEAR transfers
|
|
26
|
+
* ```typescript
|
|
27
|
+
* import { buildBatchPrivateNativeTransfer } from '@sip-protocol/sdk'
|
|
28
|
+
*
|
|
29
|
+
* const result = buildBatchPrivateNativeTransfer({
|
|
30
|
+
* transfers: [
|
|
31
|
+
* { recipientMetaAddress: 'sip:near:0x...', amount: ONE_NEAR },
|
|
32
|
+
* { recipientMetaAddress: 'sip:near:0x...', amount: 2n * ONE_NEAR },
|
|
33
|
+
* ],
|
|
34
|
+
* hideAmounts: true,
|
|
35
|
+
* })
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @packageDocumentation
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import type { StealthAddress, StealthMetaAddress } from '@sip-protocol/types'
|
|
42
|
+
import { ValidationError } from '../../errors'
|
|
43
|
+
import { generateNEARStealthAddress, parseNEARStealthMetaAddress } from './stealth'
|
|
44
|
+
import { createAnnouncementMemo } from './types'
|
|
45
|
+
import { commitNEAR, verifyOpeningNEAR, type NEARPedersenCommitment } from './commitment'
|
|
46
|
+
import type {
|
|
47
|
+
NEARAction,
|
|
48
|
+
NEARTransferAction,
|
|
49
|
+
NEARPrivateTransferBuild,
|
|
50
|
+
} from './implicit-account'
|
|
51
|
+
|
|
52
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Parameters for a privacy-wrapped native NEAR transfer with commitment
|
|
56
|
+
*/
|
|
57
|
+
export interface PrivateNativeTransferWithCommitmentParams {
|
|
58
|
+
/** Recipient's stealth meta-address */
|
|
59
|
+
recipientMetaAddress: StealthMetaAddress | string
|
|
60
|
+
/** Amount in yoctoNEAR */
|
|
61
|
+
amount: bigint
|
|
62
|
+
/** Whether to create a Pedersen commitment for the amount (default: true) */
|
|
63
|
+
hideAmount?: boolean
|
|
64
|
+
/** Optional pre-generated blinding factor */
|
|
65
|
+
blinding?: Uint8Array
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Result of building a privacy-wrapped native NEAR transfer with commitment
|
|
70
|
+
*/
|
|
71
|
+
export interface PrivateNativeTransferWithCommitmentResult {
|
|
72
|
+
/** The transfer build (actions, stealth address, etc.) */
|
|
73
|
+
transfer: NEARPrivateTransferBuild
|
|
74
|
+
/** Amount commitment (if hideAmount is true) */
|
|
75
|
+
commitment?: NEARPedersenCommitment
|
|
76
|
+
/** The stealth address */
|
|
77
|
+
stealthAddress: StealthAddress
|
|
78
|
+
/** NEAR implicit account ID */
|
|
79
|
+
stealthAccountId: string
|
|
80
|
+
/** Minimum balance for account creation */
|
|
81
|
+
minimumBalance: bigint
|
|
82
|
+
/** Whether transfer amount meets minimum balance */
|
|
83
|
+
meetsMinimum: boolean
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Parameters for batch native NEAR transfer
|
|
88
|
+
*/
|
|
89
|
+
export interface BatchNativeTransferParams {
|
|
90
|
+
/** List of transfers to execute */
|
|
91
|
+
transfers: Array<{
|
|
92
|
+
/** Recipient's stealth meta-address */
|
|
93
|
+
recipientMetaAddress: StealthMetaAddress | string
|
|
94
|
+
/** Amount in yoctoNEAR */
|
|
95
|
+
amount: bigint
|
|
96
|
+
}>
|
|
97
|
+
/** Create commitments for amounts */
|
|
98
|
+
hideAmounts?: boolean
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Result of building batch native NEAR transfers
|
|
103
|
+
*/
|
|
104
|
+
export interface BatchNativeTransferResult {
|
|
105
|
+
/** Individual transfer results */
|
|
106
|
+
transfers: Array<{
|
|
107
|
+
stealthAddress: StealthAddress
|
|
108
|
+
stealthAccountId: string
|
|
109
|
+
announcementMemo: string
|
|
110
|
+
amount: bigint
|
|
111
|
+
commitment?: NEARPedersenCommitment
|
|
112
|
+
}>
|
|
113
|
+
/** Combined actions for all transfers (must be sent as separate txs) */
|
|
114
|
+
transactions: Array<{
|
|
115
|
+
receiverId: string
|
|
116
|
+
actions: NEARAction[]
|
|
117
|
+
}>
|
|
118
|
+
/** Total amount being transferred */
|
|
119
|
+
totalAmount: bigint
|
|
120
|
+
/** Number of transfers that meet minimum balance */
|
|
121
|
+
validTransferCount: number
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Parameters for gas-sponsored transfer
|
|
126
|
+
*/
|
|
127
|
+
export interface GasSponsoredTransferParams {
|
|
128
|
+
/** Recipient's stealth meta-address */
|
|
129
|
+
recipientMetaAddress: StealthMetaAddress | string
|
|
130
|
+
/** Amount in yoctoNEAR to transfer */
|
|
131
|
+
amount: bigint
|
|
132
|
+
/** Relayer account that will pay for gas */
|
|
133
|
+
relayerAccountId: string
|
|
134
|
+
/** Maximum gas fee the relayer will cover */
|
|
135
|
+
maxRelayerFee?: bigint
|
|
136
|
+
/** Hide amount with commitment */
|
|
137
|
+
hideAmount?: boolean
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Result of building a gas-sponsored transfer
|
|
142
|
+
*/
|
|
143
|
+
export interface GasSponsoredTransferResult {
|
|
144
|
+
/** Actions for the relayer to execute */
|
|
145
|
+
relayerActions: NEARAction[]
|
|
146
|
+
/** The stealth address */
|
|
147
|
+
stealthAddress: StealthAddress
|
|
148
|
+
/** NEAR implicit account ID */
|
|
149
|
+
stealthAccountId: string
|
|
150
|
+
/** Announcement memo */
|
|
151
|
+
announcementMemo: string
|
|
152
|
+
/** Amount commitment (if hideAmount is true) */
|
|
153
|
+
commitment?: NEARPedersenCommitment
|
|
154
|
+
/** Estimated relayer fee */
|
|
155
|
+
estimatedFee: bigint
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Account creation cost estimate
|
|
160
|
+
*/
|
|
161
|
+
export interface AccountCreationCost {
|
|
162
|
+
/** Minimum balance for account existence */
|
|
163
|
+
minimumBalance: bigint
|
|
164
|
+
/** Storage cost per byte */
|
|
165
|
+
storagePerByte: bigint
|
|
166
|
+
/** Recommended transfer amount */
|
|
167
|
+
recommendedMinimum: bigint
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Minimum balance for implicit account creation (0.00182 NEAR)
|
|
174
|
+
*/
|
|
175
|
+
export const IMPLICIT_ACCOUNT_CREATION_COST = 1_820_000_000_000_000_000_000n
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Storage cost per byte on NEAR (0.00001 NEAR = 10^19 yoctoNEAR)
|
|
179
|
+
*/
|
|
180
|
+
export const STORAGE_COST_PER_BYTE = 10_000_000_000_000_000_000n
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Recommended minimum for stealth transfers (0.01 NEAR to cover creation + storage)
|
|
184
|
+
*/
|
|
185
|
+
export const RECOMMENDED_STEALTH_MINIMUM = 10_000_000_000_000_000_000_000n
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Default gas for relayer operations (100 TGas)
|
|
189
|
+
*/
|
|
190
|
+
export const RELAYER_GAS = 100_000_000_000_000n
|
|
191
|
+
|
|
192
|
+
// ─── Privacy-Wrapped Native Transfers ─────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Build a privacy-wrapped native NEAR transfer with optional amount commitment
|
|
196
|
+
*
|
|
197
|
+
* @param params - Transfer parameters
|
|
198
|
+
* @returns Transfer build with optional commitment
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```typescript
|
|
202
|
+
* const result = buildPrivateNativeTransferWithCommitment({
|
|
203
|
+
* recipientMetaAddress: 'sip:near:0x...:0x...',
|
|
204
|
+
* amount: ONE_NEAR,
|
|
205
|
+
* hideAmount: true,
|
|
206
|
+
* })
|
|
207
|
+
*
|
|
208
|
+
* // Verify the transfer meets minimum balance
|
|
209
|
+
* if (!result.meetsMinimum) {
|
|
210
|
+
* console.warn('Transfer may fail - below minimum balance')
|
|
211
|
+
* }
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
export function buildPrivateNativeTransferWithCommitment(
|
|
215
|
+
params: PrivateNativeTransferWithCommitmentParams
|
|
216
|
+
): PrivateNativeTransferWithCommitmentResult {
|
|
217
|
+
const {
|
|
218
|
+
recipientMetaAddress,
|
|
219
|
+
amount,
|
|
220
|
+
hideAmount = true,
|
|
221
|
+
blinding,
|
|
222
|
+
} = params
|
|
223
|
+
|
|
224
|
+
// Parse meta-address if string
|
|
225
|
+
const metaAddr = typeof recipientMetaAddress === 'string'
|
|
226
|
+
? parseNEARStealthMetaAddress(recipientMetaAddress)
|
|
227
|
+
: recipientMetaAddress
|
|
228
|
+
|
|
229
|
+
// Validate chain
|
|
230
|
+
if (metaAddr.chain !== 'near') {
|
|
231
|
+
throw new ValidationError(
|
|
232
|
+
`Expected NEAR meta-address, got chain '${metaAddr.chain}'`,
|
|
233
|
+
'recipientMetaAddress'
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Validate amount
|
|
238
|
+
if (amount <= 0n) {
|
|
239
|
+
throw new ValidationError('amount must be greater than 0', 'amount')
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Generate stealth address
|
|
243
|
+
const { stealthAddress, implicitAccountId } = generateNEARStealthAddress(metaAddr)
|
|
244
|
+
|
|
245
|
+
// Create announcement memo
|
|
246
|
+
const announcementMemo = createAnnouncementMemo(
|
|
247
|
+
stealthAddress.ephemeralPublicKey,
|
|
248
|
+
stealthAddress.viewTag
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
// Create amount commitment if requested
|
|
252
|
+
let commitment: NEARPedersenCommitment | undefined
|
|
253
|
+
if (hideAmount) {
|
|
254
|
+
commitment = commitNEAR(amount, blinding)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Build transfer action
|
|
258
|
+
const actions: NEARAction[] = [
|
|
259
|
+
{
|
|
260
|
+
type: 'Transfer',
|
|
261
|
+
params: {
|
|
262
|
+
deposit: amount,
|
|
263
|
+
} as NEARTransferAction,
|
|
264
|
+
},
|
|
265
|
+
]
|
|
266
|
+
|
|
267
|
+
const transfer: NEARPrivateTransferBuild = {
|
|
268
|
+
stealthAddress,
|
|
269
|
+
stealthAccountId: implicitAccountId,
|
|
270
|
+
announcementMemo,
|
|
271
|
+
actions,
|
|
272
|
+
receiverId: implicitAccountId,
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
transfer,
|
|
277
|
+
commitment,
|
|
278
|
+
stealthAddress,
|
|
279
|
+
stealthAccountId: implicitAccountId,
|
|
280
|
+
minimumBalance: IMPLICIT_ACCOUNT_CREATION_COST,
|
|
281
|
+
meetsMinimum: amount >= IMPLICIT_ACCOUNT_CREATION_COST,
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Build batch privacy-wrapped native NEAR transfers
|
|
287
|
+
*
|
|
288
|
+
* Note: Unlike token batch transfers, native NEAR transfers to different
|
|
289
|
+
* accounts cannot be batched in a single transaction. This returns
|
|
290
|
+
* separate transactions for each transfer.
|
|
291
|
+
*
|
|
292
|
+
* @param params - Batch transfer parameters
|
|
293
|
+
* @returns Batch transfer build with individual transactions
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* ```typescript
|
|
297
|
+
* const result = buildBatchPrivateNativeTransfer({
|
|
298
|
+
* transfers: [
|
|
299
|
+
* { recipientMetaAddress: meta1, amount: ONE_NEAR },
|
|
300
|
+
* { recipientMetaAddress: meta2, amount: 2n * ONE_NEAR },
|
|
301
|
+
* ],
|
|
302
|
+
* hideAmounts: true,
|
|
303
|
+
* })
|
|
304
|
+
*
|
|
305
|
+
* // Send each transaction
|
|
306
|
+
* for (const tx of result.transactions) {
|
|
307
|
+
* await sendTransaction(tx.receiverId, tx.actions)
|
|
308
|
+
* }
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
export function buildBatchPrivateNativeTransfer(
|
|
312
|
+
params: BatchNativeTransferParams
|
|
313
|
+
): BatchNativeTransferResult {
|
|
314
|
+
const {
|
|
315
|
+
transfers,
|
|
316
|
+
hideAmounts = false,
|
|
317
|
+
} = params
|
|
318
|
+
|
|
319
|
+
// Validate transfers
|
|
320
|
+
if (!transfers || transfers.length === 0) {
|
|
321
|
+
throw new ValidationError('At least one transfer is required', 'transfers')
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (transfers.length > 100) {
|
|
325
|
+
throw new ValidationError(
|
|
326
|
+
'Maximum 100 transfers per batch',
|
|
327
|
+
'transfers'
|
|
328
|
+
)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const results: BatchNativeTransferResult['transfers'] = []
|
|
332
|
+
const transactions: BatchNativeTransferResult['transactions'] = []
|
|
333
|
+
let totalAmount = 0n
|
|
334
|
+
let validTransferCount = 0
|
|
335
|
+
|
|
336
|
+
for (const transfer of transfers) {
|
|
337
|
+
const { recipientMetaAddress, amount } = transfer
|
|
338
|
+
|
|
339
|
+
// Parse meta-address if string
|
|
340
|
+
const metaAddr = typeof recipientMetaAddress === 'string'
|
|
341
|
+
? parseNEARStealthMetaAddress(recipientMetaAddress)
|
|
342
|
+
: recipientMetaAddress
|
|
343
|
+
|
|
344
|
+
// Validate chain
|
|
345
|
+
if (metaAddr.chain !== 'near') {
|
|
346
|
+
throw new ValidationError(
|
|
347
|
+
`Expected NEAR meta-address, got chain '${metaAddr.chain}'`,
|
|
348
|
+
'recipientMetaAddress'
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Validate amount
|
|
353
|
+
if (amount <= 0n) {
|
|
354
|
+
throw new ValidationError('amount must be greater than 0', 'amount')
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Generate stealth address
|
|
358
|
+
const { stealthAddress, implicitAccountId } = generateNEARStealthAddress(metaAddr)
|
|
359
|
+
|
|
360
|
+
// Create announcement memo
|
|
361
|
+
const announcementMemo = createAnnouncementMemo(
|
|
362
|
+
stealthAddress.ephemeralPublicKey,
|
|
363
|
+
stealthAddress.viewTag
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
// Create commitment if hiding amounts
|
|
367
|
+
let commitment: NEARPedersenCommitment | undefined
|
|
368
|
+
if (hideAmounts) {
|
|
369
|
+
commitment = commitNEAR(amount)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Build transfer action
|
|
373
|
+
const actions: NEARAction[] = [
|
|
374
|
+
{
|
|
375
|
+
type: 'Transfer',
|
|
376
|
+
params: {
|
|
377
|
+
deposit: amount,
|
|
378
|
+
} as NEARTransferAction,
|
|
379
|
+
},
|
|
380
|
+
]
|
|
381
|
+
|
|
382
|
+
transactions.push({
|
|
383
|
+
receiverId: implicitAccountId,
|
|
384
|
+
actions,
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
results.push({
|
|
388
|
+
stealthAddress,
|
|
389
|
+
stealthAccountId: implicitAccountId,
|
|
390
|
+
announcementMemo,
|
|
391
|
+
amount,
|
|
392
|
+
commitment,
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
totalAmount += amount
|
|
396
|
+
|
|
397
|
+
if (amount >= IMPLICIT_ACCOUNT_CREATION_COST) {
|
|
398
|
+
validTransferCount++
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
transfers: results,
|
|
404
|
+
transactions,
|
|
405
|
+
totalAmount,
|
|
406
|
+
validTransferCount,
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// ─── Gas Sponsorship ──────────────────────────────────────────────────────────
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Build a gas-sponsored transfer for sender anonymity
|
|
414
|
+
*
|
|
415
|
+
* In a gas-sponsored transfer, a relayer pays for gas on behalf of the sender,
|
|
416
|
+
* helping preserve sender anonymity as the sender's account doesn't need NEAR
|
|
417
|
+
* for gas.
|
|
418
|
+
*
|
|
419
|
+
* @param params - Gas-sponsored transfer parameters
|
|
420
|
+
* @returns Transfer build for relayer execution
|
|
421
|
+
*
|
|
422
|
+
* @example
|
|
423
|
+
* ```typescript
|
|
424
|
+
* // User builds sponsored transfer
|
|
425
|
+
* const result = buildGasSponsoredTransfer({
|
|
426
|
+
* recipientMetaAddress: 'sip:near:0x...',
|
|
427
|
+
* amount: ONE_NEAR,
|
|
428
|
+
* relayerAccountId: 'relayer.near',
|
|
429
|
+
* hideAmount: true,
|
|
430
|
+
* })
|
|
431
|
+
*
|
|
432
|
+
* // User sends signed actions to relayer
|
|
433
|
+
* // Relayer executes on behalf of user
|
|
434
|
+
* ```
|
|
435
|
+
*/
|
|
436
|
+
export function buildGasSponsoredTransfer(
|
|
437
|
+
params: GasSponsoredTransferParams
|
|
438
|
+
): GasSponsoredTransferResult {
|
|
439
|
+
const {
|
|
440
|
+
recipientMetaAddress,
|
|
441
|
+
amount,
|
|
442
|
+
relayerAccountId,
|
|
443
|
+
maxRelayerFee = 100_000_000_000_000_000_000_000n, // 0.1 NEAR default max
|
|
444
|
+
hideAmount = false,
|
|
445
|
+
} = params
|
|
446
|
+
|
|
447
|
+
// Parse meta-address if string
|
|
448
|
+
const metaAddr = typeof recipientMetaAddress === 'string'
|
|
449
|
+
? parseNEARStealthMetaAddress(recipientMetaAddress)
|
|
450
|
+
: recipientMetaAddress
|
|
451
|
+
|
|
452
|
+
// Validate chain
|
|
453
|
+
if (metaAddr.chain !== 'near') {
|
|
454
|
+
throw new ValidationError(
|
|
455
|
+
`Expected NEAR meta-address, got chain '${metaAddr.chain}'`,
|
|
456
|
+
'recipientMetaAddress'
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Validate amount
|
|
461
|
+
if (amount <= 0n) {
|
|
462
|
+
throw new ValidationError('amount must be greater than 0', 'amount')
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Validate relayer account ID (must be a valid named account)
|
|
466
|
+
if (!relayerAccountId || relayerAccountId.length < 2) {
|
|
467
|
+
throw new ValidationError('Invalid relayerAccountId', 'relayerAccountId')
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Generate stealth address
|
|
471
|
+
const { stealthAddress, implicitAccountId } = generateNEARStealthAddress(metaAddr)
|
|
472
|
+
|
|
473
|
+
// Create announcement memo
|
|
474
|
+
const announcementMemo = createAnnouncementMemo(
|
|
475
|
+
stealthAddress.ephemeralPublicKey,
|
|
476
|
+
stealthAddress.viewTag
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
// Create commitment if hiding amounts
|
|
480
|
+
let commitment: NEARPedersenCommitment | undefined
|
|
481
|
+
if (hideAmount) {
|
|
482
|
+
commitment = commitNEAR(amount)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Estimated gas fee (conservative estimate)
|
|
486
|
+
const estimatedFee = RELAYER_GAS / 1_000_000n // ~0.0001 NEAR gas cost
|
|
487
|
+
|
|
488
|
+
// Validate fee doesn't exceed max
|
|
489
|
+
if (estimatedFee > maxRelayerFee) {
|
|
490
|
+
throw new ValidationError(
|
|
491
|
+
`Estimated fee ${estimatedFee} exceeds maxRelayerFee ${maxRelayerFee}`,
|
|
492
|
+
'maxRelayerFee'
|
|
493
|
+
)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Build relayer actions
|
|
497
|
+
// The relayer will execute a function call to a relay contract
|
|
498
|
+
// that handles the transfer
|
|
499
|
+
const relayerActions: NEARAction[] = [
|
|
500
|
+
{
|
|
501
|
+
type: 'Transfer',
|
|
502
|
+
params: {
|
|
503
|
+
deposit: amount,
|
|
504
|
+
} as NEARTransferAction,
|
|
505
|
+
},
|
|
506
|
+
]
|
|
507
|
+
|
|
508
|
+
return {
|
|
509
|
+
relayerActions,
|
|
510
|
+
stealthAddress,
|
|
511
|
+
stealthAccountId: implicitAccountId,
|
|
512
|
+
announcementMemo,
|
|
513
|
+
commitment,
|
|
514
|
+
estimatedFee,
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// ─── Account Creation Helpers ─────────────────────────────────────────────────
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Get account creation cost estimate
|
|
522
|
+
*
|
|
523
|
+
* @returns Cost estimate for creating an implicit account
|
|
524
|
+
*/
|
|
525
|
+
export function getAccountCreationCost(): AccountCreationCost {
|
|
526
|
+
return {
|
|
527
|
+
minimumBalance: IMPLICIT_ACCOUNT_CREATION_COST,
|
|
528
|
+
storagePerByte: STORAGE_COST_PER_BYTE,
|
|
529
|
+
recommendedMinimum: RECOMMENDED_STEALTH_MINIMUM,
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Check if an amount meets the minimum balance for account creation
|
|
535
|
+
*
|
|
536
|
+
* @param amount - Amount in yoctoNEAR
|
|
537
|
+
* @returns True if amount meets minimum balance
|
|
538
|
+
*/
|
|
539
|
+
export function meetsMinimumBalance(amount: bigint): boolean {
|
|
540
|
+
return amount >= IMPLICIT_ACCOUNT_CREATION_COST
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Calculate the recommended transfer amount
|
|
545
|
+
*
|
|
546
|
+
* Ensures the transfer amount is sufficient for account creation
|
|
547
|
+
* and includes a buffer for storage costs.
|
|
548
|
+
*
|
|
549
|
+
* @param desiredAmount - The desired transfer amount
|
|
550
|
+
* @returns Recommended amount (at least the minimum)
|
|
551
|
+
*/
|
|
552
|
+
export function calculateRecommendedAmount(desiredAmount: bigint): bigint {
|
|
553
|
+
return desiredAmount >= RECOMMENDED_STEALTH_MINIMUM
|
|
554
|
+
? desiredAmount
|
|
555
|
+
: RECOMMENDED_STEALTH_MINIMUM
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Adjust transfer amount to ensure it meets minimum requirements
|
|
560
|
+
*
|
|
561
|
+
* @param amount - Original amount
|
|
562
|
+
* @param ensureMinimum - Whether to adjust to minimum if below
|
|
563
|
+
* @returns Adjusted amount
|
|
564
|
+
*/
|
|
565
|
+
export function adjustTransferAmount(
|
|
566
|
+
amount: bigint,
|
|
567
|
+
ensureMinimum: boolean = true
|
|
568
|
+
): { amount: bigint; adjusted: boolean; originalAmount: bigint } {
|
|
569
|
+
if (ensureMinimum && amount < IMPLICIT_ACCOUNT_CREATION_COST) {
|
|
570
|
+
return {
|
|
571
|
+
amount: IMPLICIT_ACCOUNT_CREATION_COST,
|
|
572
|
+
adjusted: true,
|
|
573
|
+
originalAmount: amount,
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return {
|
|
578
|
+
amount,
|
|
579
|
+
adjusted: false,
|
|
580
|
+
originalAmount: amount,
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// ─── Transfer Amount Formatting ───────────────────────────────────────────────
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Format NEAR amount for display
|
|
588
|
+
*
|
|
589
|
+
* @param amount - Amount in yoctoNEAR
|
|
590
|
+
* @returns Formatted string (e.g., "1.5 NEAR")
|
|
591
|
+
*/
|
|
592
|
+
export function formatNEARAmount(amount: bigint): string {
|
|
593
|
+
const str = amount.toString().padStart(25, '0')
|
|
594
|
+
const whole = str.slice(0, -24) || '0'
|
|
595
|
+
const fraction = str.slice(-24).replace(/0+$/, '')
|
|
596
|
+
|
|
597
|
+
const formatted = fraction ? `${whole}.${fraction}` : whole
|
|
598
|
+
return `${formatted} NEAR`
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Parse NEAR amount from display string
|
|
603
|
+
*
|
|
604
|
+
* @param displayAmount - Human-readable amount (e.g., "1.5" or "1.5 NEAR")
|
|
605
|
+
* @returns Amount in yoctoNEAR
|
|
606
|
+
*/
|
|
607
|
+
export function parseNEARAmount(displayAmount: string): bigint {
|
|
608
|
+
// Remove NEAR suffix and any whitespace
|
|
609
|
+
const cleaned = displayAmount.replace(/\s*NEAR\s*/i, '').trim()
|
|
610
|
+
|
|
611
|
+
const [whole, fraction = ''] = cleaned.split('.')
|
|
612
|
+
const paddedFraction = fraction.padEnd(24, '0').slice(0, 24)
|
|
613
|
+
|
|
614
|
+
return BigInt(whole + paddedFraction)
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// ─── Transfer Commitment Verification ─────────────────────────────────────────
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Verify a native NEAR transfer commitment
|
|
621
|
+
*
|
|
622
|
+
* @param commitment - The commitment to verify
|
|
623
|
+
* @param expectedAmount - The expected amount in yoctoNEAR
|
|
624
|
+
* @returns True if commitment opens to expected amount
|
|
625
|
+
*/
|
|
626
|
+
export function verifyNativeTransferCommitment(
|
|
627
|
+
commitment: NEARPedersenCommitment,
|
|
628
|
+
expectedAmount: bigint
|
|
629
|
+
): boolean {
|
|
630
|
+
return verifyOpeningNEAR(commitment.commitment, expectedAmount, commitment.blinding)
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Create a commitment proof for a native NEAR transfer
|
|
635
|
+
*
|
|
636
|
+
* This can be shared off-chain with the recipient to prove
|
|
637
|
+
* the transfer amount without revealing it on-chain.
|
|
638
|
+
*
|
|
639
|
+
* @param amount - The transfer amount
|
|
640
|
+
* @param blinding - Optional blinding factor
|
|
641
|
+
* @returns Commitment proof
|
|
642
|
+
*/
|
|
643
|
+
export function createTransferCommitmentProof(
|
|
644
|
+
amount: bigint,
|
|
645
|
+
blinding?: Uint8Array
|
|
646
|
+
): {
|
|
647
|
+
commitment: NEARPedersenCommitment
|
|
648
|
+
amount: bigint
|
|
649
|
+
amountFormatted: string
|
|
650
|
+
} {
|
|
651
|
+
const commitment = commitNEAR(amount, blinding)
|
|
652
|
+
|
|
653
|
+
return {
|
|
654
|
+
commitment,
|
|
655
|
+
amount,
|
|
656
|
+
amountFormatted: formatNEARAmount(amount),
|
|
657
|
+
}
|
|
658
|
+
}
|